text:
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Unit\Http;

use GuzzleHttp\Exception\ClientException;
use LaraCrafts\UrlShortener\Http\ShorteStShortener;

class ShorteStShortenerTest extends HttpTestCase
{
    /**
     * @var \LaraCrafts\UrlShortener\Http\ShorteStShortener
     */
    protected $shortener;

    /**
     * {@inheritDoc}
     */
    public function setUp(): void
    {
        parent::setUp();

        $this->shortener = new ShorteStShortener($this->client, 'API_TOKEN');
    }

    /**
     * Test shortening of URLs through Shorte.st.
     *
     * @return void
     */
    public function testShorten()
    {
        $this->client->queue(require __DIR__ . '/../../Fixtures/shorte_st/shorten.http-200.php');

        $this->shortener->shorten('https://google.com');
        $request = $this->client->getRequest(0);

        $this->assertNotNull($request);
        $this->assertEquals('PUT', $request->getMethod());
        $this->assertEquals('api.shorte.st', $request->getUri()->getHost());
        $this->assertEquals('/v1/data/url', $request->getRequestTarget());
        $this->assertEquals('API_TOKEN', $request->getHeader('Public-API-Token')[0]);
        $this->assertEquals('urlToShorten=https%3A%2F%2Fgoogle.com', $request->getBody()->getContents());
    }

    /**
     * Test failure of shortening through Shorte.st.
     *
     * @return void
     * @depends testShorten
     */
    public function testFailure()
    {
        $this->client->queue(require __DIR__ . '/../../Fixtures/shorte_st/shorten.http-400.php');

        $this->expectException(ClientException::class);
        $this->shortener->shorten('https://google.com');
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Http;

use GuzzleHttp\ClientInterface;
use function GuzzleHttp\json_decode;
use GuzzleHttp\Psr7\Request;
use Illuminate\Support\Arr;
use Psr\Http\Message\ResponseInterface;

class BitLyShortener extends RemoteShortener
{
    protected $client;
    protected $defaults;

    /**
     * Create a new Bit.ly shortener.
     *
     * @param \GuzzleHttp\ClientInterface $client
     * @param string $token
     * @param string $domain
     * @return void
     */
    public function __construct(ClientInterface $client, string $token, string $domain)
    {
        $this->client = $client;
        $this->defaults = [
            'allow_redirects' => false,
            'base_uri' => 'https://api-ssl.bitly.com',
            'headers' => [
                'Accept' => 'application/json',
                'Authorization' => "Bearer $token",
                'Content-Type' => 'application/json',
            ],
            'json' => [
                'domain' => $domain,
            ],
        ];
    }

    /**
     * {@inheritDoc}
     */
    public function shortenAsync($url, array $options = [])
    {
        $options = array_merge_recursive(Arr::add($this->defaults, 'json.long_url', $url), $options);
        $request = new Request('POST', '/v4/shorten');

        return $this->client->sendAsync($request, $options)->then(function (ResponseInterface $response) {
            return str_replace('http://', 'https://', json_decode($response->getBody()->getContents())->link);
        });
    }
}
<file_sep><?php

use function GuzzleHttp\Psr7\parse_response;

return parse_response(trim('
HTTP/1.1 400 Bad Request
Date: Fri, 07 Jun 2019 20:09:35 GMT
Content-Type: text/plain;charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: __cfduid=d1b584c6da86f8ac69e8adf86f419452e1559938174; expires=Sat, 06-Jun-20 20:09:34 GMT; path=/; domain=.polr.me; HttpOnly
X-Powered-By: PHP/7.1.6
Cache-Control: no-cache
Access-Control-Allow-Origin: *
Server: cloudflare
CF-RAY: 4e3543b93c08d915-AMS

400 Invalid or missing parameters.
'));
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Unit\Http;

use LaraCrafts\UrlShortener\Http\OuoIoShortener;

class OuoIoShortenerTest extends HttpTestCase
{
    /**
     * @var \LaraCrafts\UrlShortener\Http\OuoIoShortener
     */
    protected $shortener;

    /**
     * {@inheritDoc}
     */
    public function setUp(): void
    {
        parent::setUp();

        $this->shortener = new OuoIoShortener($this->client, 'API_KEY');
    }

    /**
     * Test shortening of URLs through Ouo.io.
     *
     * @return void
     */
    public function testShorten()
    {
        $this->client->queue(require __DIR__ . '/../../Fixtures/ouo_io/shorten.http-200.php');

        $shortenedUrl = $this->shortener->shorten('https://google.com');
        $request = $this->client->getRequest(0);

        $this->assertNotNull($request);
        $this->assertEquals('GET', $request->getMethod());
        $this->assertEquals('ouo.io', $request->getUri()->getHost());
        $this->assertEquals('/api/API_KEY?s=https%3A%2F%2Fgoogle.com', $request->getRequestTarget());
        $this->assertEquals('https://ouo.io/lpfqW3o', $shortenedUrl);
    }
}
<file_sep><?php

use function GuzzleHttp\Psr7\parse_response;

return parse_response(trim('
HTTP/2 400
date: Sun, 09 Jun 2019 21:19:17 GMT
content-type: text/plain;charset=UTF-8
content-length: 5
set-cookie: __cfduid=def5578458366720fe31b2319fd7f66691560115156; expires=Mon, 08-Jun-20 21:19:16 GMT; path=/; domain=.tinyurl.com; HttpOnly
cache-control: max-age=7200
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 4e462491e956d901-AMS

Error
'));
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Unit\Http;

use GuzzleHttp\Exception\ClientException;
use LaraCrafts\UrlShortener\Http\TinyUrlShortener;

class TinyUrlShortenerTest extends HttpTestCase
{
    /**
     * @var \LaraCrafts\UrlShortener\Http\TinyUrlShortener
     */
    protected $shortener;

    /**
     * {@inheritDoc}
     */
    public function setUp(): void
    {
        parent::setUp();

        $this->shortener = new TinyUrlShortener($this->client);
    }

    /**
     * Test shortening of URLs through TinyURL.
     *
     * @return void
     */
    public function testShorten()
    {
        $this->client->queue(require __DIR__ . '/../../Fixtures/tiny_url/shorten.http-200.php');

        $shortenedUrl = $this->shortener->shorten('https://google.com');
        $request = $this->client->getRequest(0);

        $this->assertNotNull($request);
        $this->assertEquals('GET', $request->getMethod());
        $this->assertEquals('tinyurl.com', $request->getUri()->getHost());
        $this->assertEquals('/api-create.php?url=https%3A%2F%2Fgoogle.com', $request->getRequestTarget());
        $this->assertEquals('https://tinyurl.com/mbq3m', $shortenedUrl);
    }

    /**
     * Test failure of shortening through TinyURL.
     *
     * @return void
     * @depends testShorten
     */
    public function testFailure()
    {
        $this->client->queue(require __DIR__ . '/../../Fixtures/tiny_url/shorten.http-400.php');

        $this->expectException(ClientException::class);
        $this->shortener->shorten('https://google.com');
    }
}
<file_sep><?php

use function GuzzleHttp\Psr7\parse_response;

return parse_response(trim('
HTTP/2 200
date: Sat, 01 Jun 2019 06:40:07 GMT
content-type: text/html; charset=UTF-8
set-cookie: __cfduid=d0cf4961cf498445e99cd9a4d9564f3291559371207; expires=Sun, 31-May-20 06:40:07 GMT; path=/; domain=.is.gd; HttpOnly; Secure
expect-ct: max-age=604800, report-uri=\'https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\'
server: cloudflare
cf-ray: 4dff31bc2b35d8f5-AMS

https://is.gd/jAxBiv
'));
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Constraints;

use GuzzleHttp\Client;
use GuzzleHttp\TransferStats;
use PHPUnit\Framework\Constraint\Constraint;

class RedirectsTo extends Constraint
{
    protected $client;
    protected $destination;
    protected $redirects;

    /**
     * Create a new RedirectsTo constraint.
     *
     * @param \Psr\Http\Message\UriInterface|string $destination
     * @param int $redirects
     * @return void
     */
    public function __construct($destination, int $redirects = 1)
    {
        parent::__construct();

        $this->client = new Client();
        $this->destination = rtrim($destination, '/');
        $this->redirects = $redirects;
    }

    /**
     * {@inheritDoc}
     */
    public function evaluate($other, $description = '', $returnResult = false)
    {
        $stack = [];

        $this->client->get($other, [
            'allow_redirects' => [
                'max' => max($this->redirects, 5),
            ],
            'on_stats' => function (TransferStats $stats) use (&$stack) {
                $stack[] = (string)$stats->getEffectiveUri();
            },
        ]);

        if (($actualRedirects = count($stack) - 1) < $this->redirects) {
            $this->fail($other, "Expected $this->redirects redirects, received $actualRedirects");
        }

        if (!$this->matches($actual = $stack[$this->redirects])) {
            $this->fail($actual, "Expected $this->destination, received $actual");
        }
    }

    /**
     * {@inheritDoc}
     */
    public function matches($other): bool
    {
        return rtrim($other, '/') === $this->destination;
    }

    /**
     * {@inheritDoc}
     */
    public function toString(): string
    {
        return 'redirects to ' . $this->exporter->export($this->destination);
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Constraints;

use PHPUnit\Framework\Constraint\Constraint;

class IsValidUrl extends Constraint
{
    /**
     * {@inheritDoc}
     */
    protected function matches($other): bool
    {
        return filter_var((string)$other, FILTER_VALIDATE_URL);
    }

    /**
     * {@inheritDoc}
     */
    public function toString(): string
    {
        return 'is a valid URL';
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Contracts;

interface Factory
{
    /**
     * Get a shortener instance.
     *
     * @param string|null $name
     * @return \LaraCrafts\UrlShortener\Contracts\Shortener
     */
    public function shortener(string $name = null);
}
<file_sep><?php

namespace LaraCrafts\UrlShortener;

use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;

class UrlShortenerServiceProvider extends ServiceProvider
{
    /**
     * Boot package services.
     *
     * @return void
     */
    public function boot()
    {
        $this->mergeConfigFrom(__DIR__ . '/../config/url-shortener.php', 'url-shortener');
        $this->publishAssets();
        $this->registerMacros();
    }

    /**
     * Publish package assets.
     *
     * @return void
     */
    protected function publishAssets()
    {
        if (!$this->app->runningInConsole() || Str::contains($this->app->version(), 'Lumen')) {
            return;
        }

        $this->publishes([
            __DIR__ . '/../config/url-shortener.php' => config_path('url-shortener.php'),
        ]);
    }

    /**
     * Register package services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->alias('url.shortener', UrlShortenerManager::class);
        $this->app->bindIf(ClientInterface::class, Client::class);

        $this->app->singleton('url.shortener', function ($app) {
            return new UrlShortenerManager($app);
        });
    }

    /**
     * Register UrlGenerator macro's.
     *
     * @return void
     */
    protected function registerMacros()
    {
        if (!class_exists(UrlGenerator::class) || !method_exists(UrlGenerator::class, 'macro')) {
            return;
        }

        UrlGenerator::macro('shorten', function (...$parameters) {
            return app('url.shortener')->shorten(...$parameters);
        });

        UrlGenerator::macro('shortenAsync', function (...$parameters) {
            return app('url.shortener')->shortenAsync(...$parameters);
        });

        UrlGenerator::macro('shortener', function () {
            return app('url.shortener');
        });
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Integration;

use GuzzleHttp\Client;
use GuzzleHttp\Promise\PromiseInterface;
use LaraCrafts\UrlShortener\Http\FirebaseShortener;
use LaraCrafts\UrlShortener\Tests\Concerns\HasUrlAssertions;
use PHPUnit\Framework\TestCase;

class FirebaseShortenerTest extends TestCase
{
    use HasUrlAssertions;

    /**
     * @var string
     */
    private $token;

    /**
     * @var string
     */
    private $prefix;

    /**
     * {@inheritDoc}
     */
    public function setUp(): void
    {
        parent::setUp();

        if (!$token = getenv('FIREBASE_API_TOKEN')) {
            $this->markTestSkipped('No Firebase API token set');
        }

        if (!$prefix = getenv('FIREBASE_URI_PREFIX')) {
            $this->markTestSkipped('No Firebase URI prefix set');
        }

        $this->token = $token;
        $this->prefix = $prefix;
    }

    /**
     * Provide suffix data.
     *
     * @return array
     */
    public function suffixProvider()
    {
        return [
            'Short' => ['SHORT'],
            'Unguessable' => ['UNGUESSABLE'],
        ];
    }

    /**
     * Test Firebase synchronous shortening.
     *
     * @param string $suffix
     * @return void
     * @dataProvider suffixProvider
     */
    public function testShorten(string $suffix)
    {
        $shortener = new FirebaseShortener(new Client, $this->token, $this->prefix, $suffix);
        $shortUrl = $shortener->shorten('https://google.com');

        $this->assertValidUrl($shortUrl);
        $this->assertRedirectsTo('https://google.com', $shortUrl, 1);
    }

    /**
     * Test Firebase asynchronous shortening.
     *
     * @param string $suffix
     * @return void
     * @dataProvider suffixProvider
     */
    public function testShortenAsync(string $suffix)
    {
        $shortener = new FirebaseShortener(new Client, $this->token, $this->prefix, $suffix);
        $promise = $shortener->shortenAsync('https://google.com');

        $this->assertInstanceOf(PromiseInterface::class, $promise);
        $this->assertValidUrl($shortUrl = $promise->wait());
        $this->assertRedirectsTo('https://google.com', $shortUrl, 1);
    }
}
<file_sep><?php

use function GuzzleHttp\Psr7\parse_response;

return parse_response(trim('
HTTP/1.1 403 Forbidden
Server: nginx
Date: Sat, 15 Jun 2019 11:55:09 GMT
Content-Type: application/json
Content-Length: 23
Connection: keep-alive
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-XSS-Protection: 1; mode=blockFilter
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src \'none

{"message":"FORBIDDEN"}
'));
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Unit\Http;

use GuzzleHttp\Exception\ClientException;
use LaraCrafts\UrlShortener\Http\FirebaseShortener;

class FirebaseShortenerTest extends HttpTestCase
{
    /**
     * @var \LaraCrafts\UrlShortener\Http\FirebaseShortener
     */
    protected $shortener;

    /**
     * {@inheritDoc}
     */
    protected function setUp(): void
    {
        parent::setUp();

        $this->shortener = new FirebaseShortener($this->client, 'API_TOKEN', 'URI_PREFIX', 'SHORT');
    }

    /**
     * Test the shortening of URLs through Firebase.
     *
     * @return void
     */
    public function testShortening()
    {
        $this->client->queue(require __DIR__ . '/../../Fixtures/firebase/shorten.http-200.php');

        $shortenedUrl = $this->shortener->shorten('https://google.com');
        $request = $this->client->getRequest(0);

        $this->assertNotNull($request);
        $this->assertEquals('POST', $request->getMethod());
        $this->assertEquals('firebasedynamiclinks.googleapis.com', $request->getUri()->getHost());
        $this->assertEquals('/v1/shortLinks?key=API_TOKEN', $request->getRequestTarget());
        $this->assertEquals('application/json', $request->getHeader('Content-Type')[0]);
        $this->assertEquals('application/json', $request->getHeader('Accept')[0]);

        $expected = '{"dynamicLinkInfo":{"domainUriPrefix":"URI_PREFIX","link":"https:\/\/google.com"},"suffix":{"option":"SHORT"}}';
        $this->assertJsonStringEqualsJsonString($expected, $request->getBody()->getContents());

        $this->assertEquals('https://laracrafts.page.link/NLtk', $shortenedUrl);
    }

    /**
     * Test failure to authenticate with Firebase.
     *
     * @return void
     * @depends testShortening
     */
    public function testUnauthorized()
    {
        $this->client->queue(require __DIR__ . '/../../Fixtures/firebase/shorten.http-400.php');

        $this->expectException(ClientException::class);
        $this->shortener->shorten('https://google.com');
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Http;

use GuzzleHttp\ClientInterface;
use function GuzzleHttp\json_decode;
use GuzzleHttp\Psr7\Request;
use Illuminate\Support\Arr;
use Psr\Http\Message\ResponseInterface;

class FirebaseShortener extends RemoteShortener
{
    protected $client;
    protected $defaults;

    /**
     * Create a new Firebase shortener.
     *
     * @param \GuzzleHttp\ClientInterface $client
     * @param string $token
     * @param string $domain
     * @param string $suffix
     * @return void
     */
    public function __construct(ClientInterface $client, string $token, string $domain, string $suffix)
    {
        $this->client = $client;
        $this->defaults = [
            'allow_redirects' => false,
            'base_uri' => 'https://firebasedynamiclinks.googleapis.com',
            'headers' => [
                'Content-Type' => 'application/json',
                'Accept' => 'application/json',
            ],
            'query' => [
                'key' => $token,
            ],
            'json' => [
                'dynamicLinkInfo' => [
                    'domainUriPrefix' => $domain,
                ],
                'suffix' => [
                    "option" => $suffix,
                ],
            ],
        ];
    }

    /**
     * {@inheritDoc}
     */
    public function shortenAsync($url, array $options = [])
    {
        $options = array_merge_recursive(Arr::add($this->defaults, 'json.dynamicLinkInfo.link', $url), $options);
        $request = new Request('POST', '/v1/shortLinks');

        return $this->client->sendAsync($request, $options)->then(function (ResponseInterface $response) {
            return json_decode($response->getBody()->getContents())->shortLink;
        });
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Http;

use GuzzleHttp\ClientInterface;
use GuzzleHttp\Psr7\Request;
use Illuminate\Support\Arr;
use Psr\Http\Message\ResponseInterface;

class OuoIoShortener extends RemoteShortener
{
    protected $client;
    protected $defaults;
    protected $token;

    /**
     * Create a new Ouo.io shortener.
     *
     * @param \GuzzleHttp\ClientInterface $client
     * @param string $token
     * @return void
     */
    public function __construct(ClientInterface $client, string $token)
    {
        $this->client = $client;
        $this->defaults = [
            'allow_redirects' => false,
            'base_uri' => 'https://ouo.io',
        ];
        $this->token = $token;
    }

    /**
     * {@inheritDoc}
     */
    public function shortenAsync($url, array $options = [])
    {
        $options = array_merge(Arr::add($this->defaults, 'query.s', $url), $options);
        $request = new Request('GET', "/api/$this->token");

        return $this->client->sendAsync($request, $options)->then(function (ResponseInterface $response) {
            return $response->getBody()->getContents();
        });
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener;

use Closure;
use GuzzleHttp\ClientInterface;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use InvalidArgumentException;
use LaraCrafts\UrlShortener\Contracts\Factory as FactoryContract;
use LaraCrafts\UrlShortener\Http\BitLyShortener;
use LaraCrafts\UrlShortener\Http\FirebaseShortener;
use LaraCrafts\UrlShortener\Http\IsGdShortener;
use LaraCrafts\UrlShortener\Http\OuoIoShortener;
use LaraCrafts\UrlShortener\Http\PolrShortener;
use LaraCrafts\UrlShortener\Http\ShorteStShortener;
use LaraCrafts\UrlShortener\Http\TinyUrlShortener;

/**
 * @method string shorten(\Psr\Http\Message\UriInterface|string $url, array $options = [])
 */
class UrlShortenerManager implements FactoryContract
{
    protected $app;
    protected $customCreators;
    protected $shorteners;

    /**
     * Create a new URL shortener manager instance.
     *
     * @param \Illuminate\Contracts\Foundation\Application $app
     * @return void
     */
    public function __construct(Application $app)
    {
        $this->app = $app;
        $this->customCreators = [];
        $this->shorteners = [];
    }

    /**
     * Dynamically call the default driver instance.
     *
     * @param  string  $method
     * @param  array   $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        return $this->driver()->$method(...$parameters);
    }

    /**
     * Call a custom driver creator.
     *
     * @param array $config
     * @return mixed
     */
    protected function callCustomCreator(array $config)
    {
        return $this->customCreators[$config['driver']]($this->app, $config);
    }

    /**
     * Create an instance of the Bit.ly driver.
     *
     * @param array $config
     * @return \LaraCrafts\UrlShortener\Http\BitLyShortener
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function createBitLyDriver(array $config)
    {
        return new BitLyShortener(
            $this->app->make(ClientInterface::class),
            Arr::get($config, 'token'),
            Arr::get($config, 'domain', 'bit.ly')
        );
    }

    /**
     * Create an instance of the Firebase driver.
     *
     * @param array $config
     * @return \LaraCrafts\UrlShortener\Http\FirebaseShortener
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function createFirebaseDriver(array $config)
    {
        return new FirebaseShortener(
            $this->app->make(ClientInterface::class),
            Arr::get($config, 'token'),
            Arr::get($config, 'prefix'),
            Arr::get($config, 'suffix')
        );
    }

    /**
     * Create an instance of the Is.gd driver.
     *
     * @param array $config
     * @return \LaraCrafts\UrlShortener\Http\IsGdShortener
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function createIsGdDriver(array $config)
    {
        return new IsGdShortener(
            $this->app->make(ClientInterface::class),
            Arr::get($config, 'base_uri'),
            Arr::get($config, 'statistics')
        );
    }

    /**
     * Create an instance of the Ouo.io driver.
     *
     * @param array $config
     * @return \LaraCrafts\UrlShortener\Http\OuoIoShortener
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function createOuoIoDriver(array $config)
    {
        return new OuoIoShortener(
            $this->app->make(ClientInterface::class),
            Arr::get($config, 'token')
        );
    }

    /**
     * Create an instance of the Polr driver.
     *
     * @param array $config
     * @return \LaraCrafts\UrlShortener\Http\PolrShortener
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function createPolrDriver(array $config)
    {
        return new PolrShortener(
            $this->app->make(ClientInterface::class),
            Arr::get($config, 'token'),
            Arr::get($config, 'prefix')
        );
    }

    /**
     * Create an instance of the Shorte.st driver.
     *
     * @param array $config
     * @return \LaraCrafts\UrlShortener\Http\ShorteStShortener
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function createShorteStDriver(array $config)
    {
        return new ShorteStShortener(
            $this->app->make(ClientInterface::class),
            Arr::get($config, 'token')
        );
    }

    /**
     * Create an instance of the TinyURL driver.
     *
     * @return \LaraCrafts\UrlShortener\Http\TinyUrlShortener
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function createTinyUrlDriver()
    {
        return new TinyUrlShortener($this->app->make(ClientInterface::class));
    }

    /**
     * Get a URL shortener driver instance.
     *
     * @param string|null $name
     * @return \LaraCrafts\UrlShortener\Contracts\Shortener
     */
    public function driver(string $name = null)
    {
        return $this->shortener($name);
    }

    /**
     * Register a custom driver creator closure.
     *
     * @param string $name
     * @param \Closure $callback
     * @return $this
     */
    public function extend(string $name, Closure $callback)
    {
        $this->customCreators[$name] = $callback->bindTo($this, $this);

        return $this;
    }

    /**
     * Get the default URL shortener driver name.
     *
     * @return string
     */
    public function getDefaultDriver()
    {
        return $this->app['config']['url-shortener.default'];
    }

    /**
     * Get the URL shortener configuration.
     *
     * @param string $name
     * @return array|null
     */
    protected function getShortenerConfig(string $name)
    {
        return $this->app['config']["url-shortener.shorteners.$name"];
    }

    /**
     * Resolve the given URL shortener.
     *
     * @param string $name
     * @return \LaraCrafts\UrlShortener\Contracts\Shortener
     */
    protected function resolve(string $name)
    {
        $config = $this->getShortenerConfig($name);

        if (is_null($config) || !array_key_exists('driver', $config)) {
            throw new InvalidArgumentException("URL shortener [{$name}] is not defined");
        }

        if (array_key_exists($config['driver'], $this->customCreators)) {
            return $this->callCustomCreator($config);
        }

        $driverMethod = 'create' . Str::studly($config['driver']) . 'Driver';

        if (method_exists($this, $driverMethod)) {
            return $this->$driverMethod($config);
        }
        throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported");
    }

    /**
     * Set the default URL shortener driver name.
     *
     * @param string $name
     * @return $this
     */
    public function setDefaultDriver(string $name)
    {
        $this->app['config']['url-shortener.default'] = $name;

        return $this;
    }

    /**
     * {@inheritDoc}
     */
    public function shortener(string $name = null)
    {
        $name = $name ?: $this->getDefaultDriver();

        if (array_key_exists($name, $this->shorteners)) {
            return $this->shorteners[$name];
        }

        return $this->shorteners[$name] = $this->resolve($name);
    }
}
<file_sep><?php

use function GuzzleHttp\Psr7\parse_response;

return parse_response(trim('
HTTP/1.1 200 OK
Date: Fri, 07 Jun 2019 20:19:33 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: __cfduid=d74a3fd364bebf9c3a34a19897547d70b1559938773; expires=Sat, 06-Jun-20 20:19:33 GMT; path=/; domain=.polr.me; HttpOnly
X-Powered-By: PHP/7.1.6
Cache-Control: no-cache
Access-Control-Allow-Origin: *
Set-Cookie: laravel_session=eyJpdiI6Impkb2NvdFozZHlzMVlEMU85TmVmblE9PS<KEY>WZ<KEY>ndnFRMis<KEY>blk0cWVLUFFOXC9<KEY>mc<KEY>ZTc0ZDUxZThjNDg3Mzk0YzhiYWQzNjA4MzFmZDc<KEY>UxNzYzMTllZjNjM2UifQ%3D%3D; expires=Fri, 07-Jun-2019 22:19:33 GMT; Max-Age=7200; path=/; HttpOnly
Server: cloudflare
CF-RAY: 4e355253fbd9d8c9-AMS

{"action":"shorten","result":"http:\/\/demo.polr.me\/0"}
'));
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Unit\Http;

use GuzzleHttp\Exception\ClientException;
use LaraCrafts\UrlShortener\Http\IsGdShortener;

class IsGdShortenerTest extends HttpTestCase
{
    /**
     * @var \LaraCrafts\UrlShortener\Http\IsGdShortener
     */
    protected $shortener;

    /**
     * {@inheritDoc}
     */
    protected function setUp(): void
    {
        parent::setUp();

        $this->shortener = new IsGdShortener($this->client, 'https://is.gd', false);
    }

    /**
     * Test the shortening of URLs through is.gd
     *
     * @return void
     */
    public function testShortening()
    {
        $this->client->queue(require __DIR__ . '/../../Fixtures/is_gd/shorten.http-200.php');

        $shortenedUrl = $this->shortener->shorten('https://google.com');
        $request = $this->client->getRequest(0);

        $this->assertNotNull($request);
        $this->assertEquals('GET', $request->getMethod());
        $this->assertEquals('is.gd', $request->getUri()->getHost());

        $this->assertEquals(
            '/create.php?format=simple&logstats=0&url=https%3A%2F%2Fgoogle.com',
            $request->getRequestTarget()
        );

        $this->assertEquals('https://is.gd/jAxBiv', $shortenedUrl);
    }

    /**
     * Test failure of shortening through is.gd.
     *
     * @return void
     * @depends testShortening
     */
    public function testFailure()
    {
        $this->client->queue(require __DIR__ . '/../../Fixtures/is_gd/shorten.http-400.php');

        $this->expectException(ClientException::class);
        $this->shortener->shorten('https://google.com');
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Integration;

use GuzzleHttp\Client;
use GuzzleHttp\Promise\PromiseInterface;
use LaraCrafts\UrlShortener\Http\TinyUrlShortener;
use LaraCrafts\UrlShortener\Tests\Concerns\HasUrlAssertions;
use PHPUnit\Framework\TestCase;

class TinyUrlShortenerTest extends TestCase
{
    use HasUrlAssertions;

    /**
     * @var \LaraCrafts\UrlShortener\Http\TinyUrlShortener
     */
    protected $shortener;

    /**
     * {@inheritDoc}
     */
    public function setUp(): void
    {
        parent::setUp();
        $this->shortener = new TinyUrlShortener(new Client);
    }

    /**
     * Test TinyURL synchronous shortening.
     *
     * @return void
     */
    public function testShorten()
    {
        $shortUrl = $this->shortener->shorten('https://google.com');

        $this->assertValidUrl($shortUrl);
        $this->assertRedirectsTo('https://google.com', $shortUrl, 1);
    }

    /**
     * Test TinyURL asynchronous shortening.
     *
     * @return void
     */
    public function testShortenAsync()
    {
        $promise = $this->shortener->shortenAsync('https://google.com');

        $this->assertInstanceOf(PromiseInterface::class, $promise);
        $this->assertValidUrl($shortUrl = $promise->wait());
        $this->assertRedirectsTo('https://google.com', $shortUrl, 1);
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Unit\Http;

use LaraCrafts\UrlShortener\Tests\Concerns\HasUrlAssertions;
use PHPUnit\Framework\TestCase;

abstract class HttpTestCase extends TestCase
{
    use HasUrlAssertions;

    /**
     * @var \LaraCrafts\UrlShortener\Tests\Unit\Http\MockClient
     */
    protected $client;

    /**
     * {@inheritDoc}
     */
    protected function setUp(): void
    {
        $this->client = new MockClient();
    }

    /**
     * {@inheritDoc}
     */
    protected function tearDown(): void
    {
        if ($this->client->hasQueuedMessages()) {
            $this->fail(sprintf('HTTP client contains %d unused message(s)', $this->client->getQueueSize()));
        }
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Concerns;

use LaraCrafts\UrlShortener\Tests\Constraints\IsValidUrl;
use LaraCrafts\UrlShortener\Tests\Constraints\RedirectsTo;

trait HasUrlAssertions
{
    /**
     * Assert that the given value redirects to the expected value.
     *
     * @param \Psr\Http\Message\UriInterface|string $expected
     * @param \Psr\Http\Message\UriInterface|string $actual
     * @param int $redirects
     * @return void
     */
    public static function assertRedirectsTo($expected, $actual, int $redirects = 1)
    {
        static::assertThat($actual, static::redirectsTo($expected, $redirects));
    }

    /**
     * Assert that the given value is valid URL.
     *
     * @param mixed $actual
     */
    public static function assertValidUrl($actual)
    {
        static::assertThat($actual, static::isValidUrl());
    }

    /**
     * Create a IsValidUrl instance
     *
     * @return \LaraCrafts\UrlShortener\Tests\Constraints\IsValidUrl
     */
    public static function isValidUrl()
    {
        return new IsValidUrl;
    }

    /**
     * Create a RedirectsTo instance
     *
     * @param \Psr\Http\Message\UriInterface|string $expected
     * @param int $redirects
     * @return \LaraCrafts\UrlShortener\Tests\Constraints\RedirectsTo
     */
    public static function redirectsTo($expected, int $redirects = 1)
    {
        return new RedirectsTo($expected, $redirects);
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Contracts;

interface AsyncShortener extends Shortener
{
    /**
     * Shorten the given URL asynchronously.
     *
     * @param \Psr\Http\Message\UriInterface|string $url
     * @param array $options
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function shortenAsync($url, array $options = []);
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Http;

use GuzzleHttp\ClientInterface;
use GuzzleHttp\Psr7\Request;
use Illuminate\Support\Arr;
use Psr\Http\Message\ResponseInterface;

class IsGdShortener extends RemoteShortener
{
    protected $client;
    protected $defaults;

    /**
     * Create a new Is.gd shortener.
     *
     * @param \GuzzleHttp\ClientInterface $client
     * @param \Psr\Http\Message\UriInterface|string $baseUri
     * @param bool $statistics
     * @return void
     */
    public function __construct(ClientInterface $client, $baseUri, bool $statistics)
    {
        $this->client = $client;
        $this->defaults = [
            'allow_redirects' => false,
            'base_uri' => (string)$baseUri,
            'query' => [
                'format' => 'simple',
                'logstats' => intval($statistics),
            ],
        ];
    }

    /**
     * {@inheritDoc}
     */
    public function shortenAsync($url, array $options = [])
    {
        $options = Arr::add(array_merge_recursive($this->defaults, $options), 'query.url', $url);
        $request = new Request('GET', '/create.php');

        return $this->client->sendAsync($request, $options)->then(function (ResponseInterface $response) {
            return $response->getBody()->getContents();
        });
    }
}
<file_sep><?php

use function GuzzleHttp\Psr7\parse_response;

return parse_response(trim('
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 15 Jun 2019 12:04:01 GMT
Content-Type: application/json
Content-Length: 263
Connection: keep-alive
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-XSS-Protection: 1; mode=blockFilter
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src \'none

{"created_at":"1970-01-01T00:00:00+0000","id":"bit.ly/2WujBe0","link":"http://bit.ly/2WujBe0","custom_bitlinks":[],"long_url":"https://google.com/","archived":false,"tags":[],"deeplinks":[],"references":{"group":"https://api-ssl.bitly.com/v4/groups/Bj1tjw4uLSN"}}
'));
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Integration;

use GuzzleHttp\Client;
use GuzzleHttp\Promise\PromiseInterface;
use Illuminate\Support\Str;
use LaraCrafts\UrlShortener\Http\IsGdShortener;
use LaraCrafts\UrlShortener\Tests\Concerns\HasUrlAssertions;
use PHPUnit\Framework\TestCase;

class VGdShortenerTest extends TestCase
{
    use HasUrlAssertions;

    /**
     * @var \LaraCrafts\UrlShortener\Http\IsGdShortener
     */
    protected $shortener;

    /**
     * {@inheritDoc}
     */
    public function setUp(): void
    {
        parent::setUp();
        $this->shortener = new IsGdShortener(new Client, 'https://v.gd', false);
    }

    /**
     * Test Is.gd synchronous shortening.
     *
     * @return void
     */
    public function testShorten()
    {
        $shortUrl = $this->shortener->shorten('https://google.com');

        $this->assertValidUrl($shortUrl);
        $this->assertTrue(Str::startsWith($shortUrl, 'https://v.gd'));
    }

    /**
     * Test Is.gd asynchronous shortening.
     *
     * @return void
     */
    public function testShortenAsync()
    {
        $promise = $this->shortener->shortenAsync('https://google.com');

        $this->assertInstanceOf(PromiseInterface::class, $promise);
        $this->assertValidUrl($shortUrl = $promise->wait());
        $this->assertTrue(Str::startsWith($shortUrl, 'https://v.gd'));
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Integration;

use GuzzleHttp\Client;
use GuzzleHttp\Promise\PromiseInterface;
use LaraCrafts\UrlShortener\Http\BitLyShortener;
use LaraCrafts\UrlShortener\Tests\Concerns\HasUrlAssertions;
use PHPUnit\Framework\TestCase;

class BitLyShortenerTest extends TestCase
{
    use HasUrlAssertions;

    /**
     * @var \LaraCrafts\UrlShortener\Http\BitLyShortener
     */
    protected $shortener;

    /**
     * {@inheritDoc}
     */
    public function setUp(): void
    {
        parent::setUp();

        if (!$token = getenv('BIT_LY_API_TOKEN')) {
            $this->markTestSkipped('No Bit.ly API token set');
        }

        $this->shortener = new BitLyShortener(new Client, $token, 'bit.ly');
    }

    /**
     * Test Bit.ly synchronous shortening.
     *
     * @return void
     */
    public function testShorten()
    {
        $shortUrl = $this->shortener->shorten('https://google.com');

        $this->assertValidUrl($shortUrl);
        $this->assertRedirectsTo('https://google.com', $shortUrl, 1);
    }

    /**
     * Test Bit.ly asynchronous shortening.
     *
     * @return void
     */
    public function testShortenAsync()
    {
        $promise = $this->shortener->shortenAsync('https://google.com');

        $this->assertInstanceOf(PromiseInterface::class, $promise);
        $this->assertValidUrl($shortUrl = $promise->wait());
        $this->assertRedirectsTo('https://google.com', $shortUrl, 1);
    }
}
<file_sep><?php

use function GuzzleHttp\Psr7\parse_response;

return parse_response(trim('
HTTP/2 200
date: Sun, 09 Jun 2019 21:20:18 GMT
content-type: text/plain;charset=UTF-8
content-length: 24
set-cookie: __cfduid=d168f41c88b2aa48a03cbae57980ee8281560115218; expires=Mon, 08-Jun-20 21:20:18 GMT; path=/; domain=.tinyurl.com; HttpOnly
cache-control: max-age=3600
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 4e4626123d379c2d-AMS

http://tinyurl.com/mbq3m
'));
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Unit\Http;

use function GuzzleHttp\choose_handler;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\ResponseInterface;

class MockClient extends Client
{
    protected $handler;
    protected $history;

    /**
     * Create a new test client.
     *
     * @param array $config
     * @return void
     */
    public function __construct(array $config = [])
    {
        $this->handler = new MockHandler();
        $this->history = [];

        parent::__construct($config + ['handler' => $this->newHandlerStack($this->handler)]);
    }

    /**
     * Get the client history.
     *
     * @param int|null $at
     * @return mixed
     */
    public function getHistory(int $at = null)
    {
        if (is_null($at)) {
            return $this->history;
        }

        return $this->history[$at] ?? null;
    }

    /**
     * Get a previously made request.
     *
     * @param int $at
     * @return \Psr\Http\Message\RequestInterface|null
     */
    public function getRequest(int $at)
    {
        return $this->getHistory($at)['request'] ?? null;
    }

    /**
     * Get the amount of messages waiting in the queue.
     *
     * @return int
     */
    public function getQueueSize()
    {
        return $this->handler->count();
    }

    /**
     * Determine if there are queued messages.
     *
     * @return bool
     */
    public function hasQueuedMessages()
    {
        return $this->getQueueSize() > 0;
    }

    /**
     * Get a fresh handler stack.
     *
     * @param callable|null $handler
     * @return \GuzzleHttp\HandlerStack
     */
    protected function newHandlerStack($handler = null)
    {
        $stack = new HandlerStack($handler ?: choose_handler());
        $stack->push(Middleware::history($this->history));
        $stack->push(Middleware::httpErrors());

        return $stack;
    }

    /**
     * Queue the given response.
     *
     * @param \Psr\Http\Message\ResponseInterface ...$responses
     * @return $this
     */
    public function queue(ResponseInterface ...$responses)
    {
        $this->handler->append(...$responses);

        return $this;
    }
}
<file_sep><?php

use function GuzzleHttp\Psr7\parse_response;

return parse_response(trim('
HTTP/1.1 400 Bad Request
Vary: X-Origin
Vary: Referer
Content-Type: application/json; charset=UTF-8
Date: Sun, 23 Jun 2019 12:20:33 GMT
Server: ESF
Cache-Control: private
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Alt-Svc: quic=":443"; ma=2592000; v="46,44,43,39"
Accept-Ranges: none
Vary: Origin,Accept-Encoding
Transfer-Encoding: chunked

{
  "error": {
    "code": 400,
    "message": "Link value cannot be empty [https://firebase.google.com/docs/dynamic-links/create-manually#parameters]",
    "status": "INVALID_ARGUMENT"
  }
}
'));
<file_sep><?php

namespace LaraCrafts\UrlShortener\Http;

use LaraCrafts\UrlShortener\Contracts\AsyncShortener;

abstract class RemoteShortener implements AsyncShortener
{
    /**
     * {@inheritDoc}
     */
    public function shorten($url, array $options = [])
    {
        return $this->shortenAsync($url, $options)->wait();
    }
}
<file_sep><?php

use function GuzzleHttp\Psr7\parse_response;

return parse_response(trim('
HTTP/2 400
date: Sat, 01 Jun 2019 07:06:24 GMT
content-type: text/html; charset=UTF-8
set-cookie: __cfduid=d8ddac9f1ceba2876dd2407d383fae8af1559372784; expires=Sun, 31-May-20 07:06:24 GMT; path=/; domain=.is.gd; HttpOnly; Secure
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 4dff583d2f3fc76d-AMS

Error: Please specify a URL to shorten.
'));
<file_sep><?php

use function GuzzleHttp\Psr7\parse_response;

return parse_response(trim('
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Vary: X-Origin
Vary: Referer
Date: Sun, 23 Jun 2019 12:00:06 GMT
Server: ESF
Cache-Control: private
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Alt-Svc: quic=":443"; ma=2592000; v="46,44,43,39"
Accept-Ranges: none
Vary: Origin,Accept-Encoding
Transfer-Encoding: chunked

{
  "shortLink": "https://laracrafts.page.link/NLtk",
  "previewLink": "https://laracrafts.page.link/NLtk?d=1"
}
'));
<file_sep><?php

namespace LaraCrafts\UrlShortener\Contracts;

interface Shortener
{
    /**
     * Shorten the given URL.
     *
     * @param \Psr\Http\Message\UriInterface|string $url
     * @param array $options
     * @return string
     */
    public function shorten($url, array $options = []);
}
<file_sep># laravel-url-shortener
Powerful URL shortening tools in Laravel

<p align="center">
    <a href="https://travis-ci.org/LaraCrafts/laravel-url-shortener"><img src="https://travis-ci.org/LaraCrafts/laravel-url-shortener.svg?branch=master"></a>
    <a href="https://packagist.org/packages/laracrafts/laravel-url-shortener"><img src="https://poser.pugx.org/laracrafts/laravel-url-shortener/downloads"></a>
    <a href="https://packagist.org/packages/laracrafts/laravel-url-shortener"><img src="https://poser.pugx.org/laracrafts/laravel-url-shortener/version"></a>
    <a href="https://scrutinizer-ci.com/g/LaraCrafts/laravel-url-shortener/"><img src="https://scrutinizer-ci.com/g/LaraCrafts/laravel-url-shortener/badges/coverage.png?b=master"></a>
    <a href="https://packagist.org/packages/laracrafts/laravel-url-shortener"><img src="https://poser.pugx.org/laracrafts/laravel-url-shortener/license"></a>
</p>

- [Installation](#installation)
    - [Requirements](#requirements)
    - [Laravel 5.5+](#laravel-55)
    - [Laravel 5.1-5.4](#laravel-51-54)
- [Usage](#usage)
    - [Changing the driver](#changing-the-driver)
    - [Adding your own drivers](#adding-your-own-drivers)
- [Available drivers](#available-drivers)
    - [Bit.ly](#bitly)
    - [Firebase Dynamic Links](#firebase-dynamic-links)
    - [Is.gd](#isgd--vgd)
    - [Ouo.io](#ouoio)
    - [Polr](#polr)
    - [Shorte.st](#shortest)
    - [TinyURL](#tinyurl)
    - [V.gd](#isgd--vgd)
- [Changelog](#changelog)
- [Security](#security)
- [Contributing](#contributing)
- [Credits](#credits)
- [License](#license)

## Installation
You can easily install this package using Composer, by running the following command:

```bash
composer require laracrafts/laravel-url-shortener
```

### Requirements
This package has the following requirements:

- PHP 7.1 or higher
- Laravel 5.1 or higher

### Laravel 5.5+
If you use Laravel 5.5 or higher, that's it. You can now use the package, continue to the [usage](#usage) section.

### Laravel 5.1-5.4
If you're using an older version of Laravel, register the package's service provider to your application. You can do
this by adding the following line to your `config/app.php` file:

```php
'providers' => [
   ...
   LaraCrafts\UrlShortener\UrlShortenerServiceProvider::class,
   ...
],
```

## Usage
The shortener can be retrieved from the container in two ways:

```php
$shortener = app('url.shortener');
// or...
$shortener = url()->shortener();
```

Once you have an instance of the shortener, you can shorten your URLs:

```php
// This will return your shortened URL as a string
$shortener->shorten(...);

// This will return a promise which will resolve to your shortened URL
$shortener->shortenAsync(...);

// You can also call shortening from Laravel's url component directly
url()->shorten(...);

// or...
app('url')->shorten(...);

// or even...
app('url.shortener')->shorten(...);
```

This package relies on Guzzle's promise library for its asynchronous shortening, read their
[documentation](https://github.com/guzzle/promises) for more information.

You can also use dependency injection to inject the shortener into a method:

```php
class MyController extends Controller
{
    public function myFunction(ShortenerManager $shortener)
    {
        $shortener->shorten(...);
    }
}
```

The shortener exposes the following methods:

Method         | Description
---------------|-------------------------------------
`shorten`      | Shorten the given URL
`shortenAsync` | Shorten the given URL asynchronously
`driver`       | Retrieve a driver (e.g. `tiny_url`)
`extend`       | Register your own driver

### Changing the driver
You can change the default driver by setting `URL_SHORTENER_DRIVER={driver}` in your environment file or publishing the
config file and changing it directly.

### Adding your own drivers
Much like Laravel's [core components](https://laravel.com/docs/5.0/extending#managers-and-factories), you can add your
own drivers for this package. You can do this by adding the following code to a central place in your application
(preferably a service provider).

```php
public function boot(ShortenerManager $shorteners)
{
    $shorteners->extend('my_driver', function ($app, $config) {
       // Return your driver instance here
    });
}
```

Once you have registered your driver you can call it like any other driver.

If you wrote a custom driver that others might find useful (such as a public online shortener service), please consider
adding it to the package via a pull request.

## Available drivers
Below is a list of available drivers along with their individual specs:

Service                                           | Driver name | Since version | Analytics | Monetization
--------------------------------------------------|-------------|---------------|-----------|-----------------
[Bit.ly](#bitly)                                  | `bit_ly`    | 0.1.0         | yes       | no
[Firebase Dynamic Links](#firebase-dynamic-links) | `firebase`  | 0.2.0         | yes       | no
[Is.gd](#isgd--vgd)                               | `is_gd`     | 0.2.0         | yes       | no
[Ouo.io](#ouoio)                                  | `ouo_io`    | 0.2.0         | yes       | yes
[Polr](#polr)                                     | `polr`      | 0.3.0         | yes       | no
[Shorte.st](#shortest)                            | `shorte_st` | 0.1.0         | yes       | yes
[TinyURL](#tinyurl)                               | `tiny_url`  | 0.1.0         | no        | no
[V.gd](#isgd--vgd)                                | `is_gd`     | 0.2.0         | yes       | no

### Bit.ly
[website](https://bit.ly)

This driver runs on Bit.ly's API and currently only supports API version 4. The API requires an access token and
currently only _generic access tokens_ are supported. You can retrieve such tokens from your Bit.ly profile. If you have
a paid Bit.ly account you will also be able to set the domain for your shortened URLs.

Variable                  | Description
--------------------------|----------------------
`URL_SHORTENER_API_TOKEN` | Your Bit.ly API token
`URL_SHORTENER_PREFIX`    | Your short URL domain

### Firebase Dynamic Links
[website](https://firebase.google.com/)

This driver runs on Firebase's API. The API requires an access token, a URI prefix and a suffix. You can access these
information on you firebase console. The token accessible under the project settings as "Web API Key" and the prefixes
can be defined and accessed under the Dynamic Links menu.

The suffix can have the value `SHORT` or `UNGUESSABLE`.

> **IMPORTANT!** Links created via the API are not visible in the Firebase console. They are only accessible via
> the [Analytics REST API](https://firebase.google.com/docs/reference/dynamic-links/analytics).

Variable                 | Description                        | Default
-------------------------|------------------------------------|---------------
`URL_SHORTENER_API_TOKEN`| Your Firebase API token            |
`URL_SHORTENER_PREFIX`   | Your URL prefix                    |
`URL_SHORTENER_STRATEGY` | The path component creation method | `UNGUESSABLE`

### Is.gd / V.gd
[website](https://is.gd)

This driver supports [is.gd](https://is.gd) and [v.gd](https://v.gd) trough their respective APIs. When link previews
are enabled v.gd will be used, otherwise is.gd will be used.

Variable                  | Description
--------------------------|----------------------------------------
`URL_SHORTENER_ANALYTICS` | Enable or disable statistics

### Ouo.io
[website](https://ouo.io)

This driver uses the Ouo.io API and requires an access token. The API allows for URL monetization via advertisements and
provides analytics via its dashboard.

Variable                  | Description
--------------------------|----------------------
`URL_SHORTENER_API_TOKEN` | Your Ouo.io API token

### Polr
[website](https://github.com/cydrobolt/polr/)

This driver uses the Polr API. The API requires an access token and a URI prefix.

Variable                  | Description
--------------------------|-------------------------
`URL_SHORTENER_API_TOKEN` | Your Polr API token
`URL_SHORTENER_PREFIX`    | Your URL prefix

### Shorte.st
[website](https://shorte.st)

This driver uses the Shorte.st API, which requires an access token. This API supports monetization of your URLs and can
give you insight into your traffic via its dashboard.

Variable                  | Description
--------------------------|-------------------------
`URL_SHORTENER_API_TOKEN` | Your Shorte.st API token

### TinyURL
[website](http://tinyurl.com)

This driver runs on the TinyURL API, which requires no additional setup. This driver is the package default.

## Changelog
Please see [CHANGELOG](CHANGELOG.md) for more information what has been changed in recent versions.

## Security
If you discover any security related issues, please email one of the authors instead of using the issue tracker. You can
find the author emails in the [composer.json](composer.json).

## Contributing
Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

## Credits
- [<NAME>](https://github.com/axlon)
- [<NAME>](https://github.com/nerg4l)
- [All Contributors](../../contributors)

## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Unit\Http;

use GuzzleHttp\Exception\ClientException;
use LaraCrafts\UrlShortener\Http\PolrShortener;

class PolrShortenerTest extends HttpTestCase
{
    /**
     * @var \LaraCrafts\UrlShortener\Http\PolrShortener
     */
    protected $shortener;

    /**
     * {@inheritDoc}
     */
    public function setUp(): void
    {
        parent::setUp();

        $this->shortener = new PolrShortener($this->client, 'API_KEY', 'http://example.com');
    }

    /**
     * Test shortening of URLs through Polr.
     *
     * @return void
     */
    public function testShorten()
    {
        $this->client->queue(require __DIR__ . '/../../Fixtures/polr/shorten.http-200.php');

        $shortenedUrl = $this->shortener->shorten('https://google.com');
        $request = $this->client->getRequest(0);

        $this->assertNotNull($request);
        $this->assertEquals('GET', $request->getMethod());
        $this->assertEquals('example.com', $request->getUri()->getHost());

        $this->assertEquals(
            '/api/v2/action/shorten?key=API_KEY&response_type=json&url=https%3A%2F%2Fgoogle.com',
            $request->getRequestTarget()
        );

        $this->assertEquals('http://demo.polr.me/0', $shortenedUrl);
    }

    /**
     * Test failure of shortening through Polr.
     *
     * @return void
     * @depends testShorten
     */
    public function testFailure()
    {
        $this->client->queue(require __DIR__ . '/../../Fixtures/polr/shorten.http-400.php');

        $this->expectException(ClientException::class);
        $this->shortener->shorten('https://google.com');
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Http;

use GuzzleHttp\ClientInterface;
use function GuzzleHttp\json_decode;
use GuzzleHttp\Psr7\Request;
use Illuminate\Support\Arr;
use Psr\Http\Message\ResponseInterface;

class PolrShortener extends RemoteShortener
{
    /**
     * @var \GuzzleHttp\ClientInterface
     */
    protected $client;

    /**
     * @var array
     */
    protected $defaults;

    /**
     * Create a new Polr shortener.
     *
     * @param \GuzzleHttp\ClientInterface $client
     * @param string $token
     * @param string $domain
     * @return void
     */
    public function __construct(ClientInterface $client, string $token, string $domain)
    {
        $this->client = $client;
        $this->defaults = [
            'allow_redirects' => false,
            'base_uri' => $domain,
            'headers' => [
                'Accept' => 'application/json',
            ],
            'query' => [
                'key' => $token,
                'response_type' => 'json',
            ],
        ];
    }

    /**
     * {@inheritDoc}
     */
    public function shortenAsync($url, array $options = [])
    {
        $options = array_merge_recursive(Arr::add($this->defaults, 'query.url', $url), $options);
        $request = new Request('GET', '/api/v2/action/shorten');

        return $this->client->sendAsync($request, $options)->then(function (ResponseInterface $response) {
            return json_decode($response->getBody()->getContents())->result;
        });
    }
}
<file_sep><?php

require_once __DIR__ . '/../vendor/autoload.php';

if (!class_exists('\PHPUnit\Framework\Constraint\Constraint')) {
    class_alias('\PHPUnit_Framework_Constraint', '\PHPUnit\Framework\Constraint\Constraint');
}

if (!class_exists('\PHPUnit\Framework\TestCase')) {
    class_alias('\PHPUnit_Framework_TestCase', '\PHPUnit\Framework\TestCase');
}
<file_sep><?php

use function GuzzleHttp\Psr7\parse_response;

return parse_response(ltrim('
HTTP/1.1 400 Bad Request
Server: nginx
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
X-Powered-By: PHP/5.6.37-0+deb8u1
Set-Cookie: PHPSESSID=h9av5fl0ngpbqnvkfe0f91os61; expires=Sun, 09-Jun-2019 22:46:01 GMT; Max-Age=3600; path=/; domain=.shorte.st; HttpOnly
Cache-Control: no-cache
Date: Sun, 09 Jun 2019 21:46:01 GMT
Set-Cookie: cookies-enable=1; path=/; httponly
X-Server-ID: shn06
X-UA-Compatible: IE=Edge

'));
<file_sep># Changelog

## [v0.3.2](https://github.com/LaraCrafts/laravel-url-shortener/releases/tag/v0.3.2) (2019-09-12)
- Fix shorten function not being accessible from UrlGenerator and UrlShortenerManager

## [v0.3.1](https://github.com/LaraCrafts/laravel-url-shortener/releases/tag/v0.3.1) (2019-09-08)
- Added Laravel 6 support

## [v0.3.0](https://github.com/LaraCrafts/laravel-url-shortener/releases/tag/v0.3.0) (2019-07-07)
- Restructured config (**Breaking change**)
- Removed UrlShortener facade (**Breaking change**)
- Added Polr driver

## [v0.2.0](https://github.com/LaraCrafts/laravel-url-shortener/releases/tag/v0.2.0) (2019-04-27)
- Added UrlShortener facade
- Added Is.gd driver
- Added V.gd driver
- Added Ouo.io driver
- Added Firebase driver

## [v0.1.0](https://github.com/LaraCrafts/laravel-url-shortener/releases/tag/v0.1.0) (2019-04-19)
- Initial release
- Added TinyURL driver
- Added Bit.ly driver
- Added Shorte.st driver
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Integration;

use GuzzleHttp\Client;
use GuzzleHttp\Promise\PromiseInterface;
use Illuminate\Support\Str;
use LaraCrafts\UrlShortener\Http\OuoIoShortener;
use LaraCrafts\UrlShortener\Tests\Concerns\HasUrlAssertions;
use PHPUnit\Framework\TestCase;

class OuoIoShortenerTest extends TestCase
{
    use HasUrlAssertions;

    /**
     * @var \LaraCrafts\UrlShortener\Http\OuoIoShortener
     */
    protected $shortener;

    /**
     * {@inheritDoc}
     */
    public function setUp(): void
    {
        parent::setUp();

        if (!$token = getenv('OUO_IO_API_TOKEN')) {
            $this->markTestSkipped('No Ouo.io API token set');
        }

        $this->shortener = new OuoIoShortener(new Client, $token);
    }

    /**
     * Test Ouo.io synchronous shortening.
     *
     * @return void
     */
    public function testShorten()
    {
        $shortUrl = $this->shortener->shorten('https://google.com');

        $this->assertValidUrl($shortUrl);
        $this->assertTrue(Str::startsWith($shortUrl, 'https://ouo.io/'));
    }

    /**
     * Test Ouo.io asynchronous shortening.
     *
     * @return void
     */
    public function testShortenAsync()
    {
        $promise = $this->shortener->shortenAsync('https://google.com');

        $this->assertInstanceOf(PromiseInterface::class, $promise);
        $this->assertValidUrl($shortUrl = $promise->wait());
        $this->assertTrue(Str::startsWith($shortUrl, 'https://ouo.io/'));
    }
}
<file_sep><?php

namespace LaraCrafts\UrlShortener\Http;

use GuzzleHttp\ClientInterface;
use function GuzzleHttp\json_decode;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\ResponseInterface;

class ShorteStShortener extends RemoteShortener
{
    protected $client;
    protected $defaults;

    /**
     * Create a new Shorte.st shortener.
     *
     * @param \GuzzleHttp\ClientInterface $client
     * @param string $token
     * @return void
     */
    public function __construct(ClientInterface $client, string $token)
    {
        $this->client = $client;
        $this->defaults = [
            'allow_redirects' => false,
            'base_uri' => 'https://api.shorte.st',
            'headers' => [
                'Accept' => 'application/json',
                'Public-API-Token' => $token,
                'Content-Type' => 'application/x-www-form-urlencoded',
            ],
        ];
    }

    /**
     * {@inheritDoc}
     */
    public function shortenAsync($url, array $options = [])
    {
        $options = array_merge_recursive($this->defaults, $options);
        $request = new Request('PUT', '/v1/data/url', [], http_build_query(['urlToShorten' => $url]));

        return $this->client->sendAsync($request, $options)->then(function (ResponseInterface $response) {
            return json_decode($response->getBody()->getContents())->shortenedUrl;
        });
    }
}
<file_sep><?php

use function GuzzleHttp\Psr7\parse_response;

return parse_response(trim('
HTTP/1.1 200 OK
Server: nginx
Content-Type: application/json
Transfer-Encoding: chunked
X-Powered-By: PHP/5.6.37-0+deb8u1
Set-Cookie: PHPSESSID=u50974qaql71l37fn2edji7340; expires=Sun, 09-Jun-2019 22:42:32 GMT; Max-Age=3600; path=/; domain=.shorte.st; HttpOnly
Access-Control-Allow-Origin: *
Cache-Control: no-cache
Date: Sun, 09 Jun 2019 21:42:32 GMT
Set-Cookie: hl=en; expires=Mon, 08-Jun-2020 21:42:32 GMT; Max-Age=31536000; path=/
Set-Cookie: cookies-enable=1; path=/; httponly
X-Server-ID: shn07
X-UA-Compatible: IE=Edge

{"status":"ok","shortenedUrl":"http:\/\/ceesty.com\/w1D1ji"}
'));
<file_sep><?php

use function GuzzleHttp\Psr7\parse_response;

return parse_response(trim('
HTTP/2 200
date: Sat, 15 Jun 2019 11:42:59 GMT
content-type: text/html; charset=UTF-8
set-cookie: __cfduid=d42d736f9ab5f6d72dbd1cdcd507ccda81560598978; expires=Sun, 14-Jun-20 11:42:58 GMT; path=/; domain=.ouo.io; HttpOnly; Secure
cache-control: no-cache
set-cookie: ouoio_session=<KEY>0%3D; expires=Sat, 15-Jun-2019 13:29:59 GMT; Max-Age=7200; path=/; httponly
set-cookie: language=<KEY>ZDUyMzJkIn0%3D; expires=Thu, 13-Jun-2024 11:29:59 GMT; Max-Age=157680000; path=/; httponly
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 4e7448a279d19f1b-AMS

https://ouo.io/lpfqW3o
'));
<file_sep><?php

namespace LaraCrafts\UrlShortener\Http;

use GuzzleHttp\ClientInterface;
use GuzzleHttp\Psr7\Request;
use Illuminate\Support\Arr;
use Psr\Http\Message\ResponseInterface;

class TinyUrlShortener extends RemoteShortener
{
    protected $client;
    protected const defaults = [
        'allow_redirects' => false,
        'base_uri' => 'https://tinyurl.com',
    ];

    /**
     * Create a new TinyURL shortener.
     *
     * @param \GuzzleHttp\ClientInterface $client
     * @return void
     */
    public function __construct(ClientInterface $client)
    {
        $this->client = $client;
    }

    /**
     * {@inheritDoc}
     */
    public function shortenAsync($url, array $options = [])
    {
        $options = array_merge(Arr::add(static::defaults, 'query.url', $url), $options);
        $request = new Request('GET', '/api-create.php');

        return $this->client->sendAsync($request, $options)->then(function (ResponseInterface $response) {
            return str_replace('http://', 'https://', $response->getBody()->getContents());
        });
    }
}
<file_sep><?php

return [

    'default' => env('URL_SHORTENER_DRIVER', 'tiny_url'),

    'shorteners' => [

        'bit_ly' => [
            'driver' => 'bit_ly',
            'domain' => env('URL_SHORTENER_PREFIX', 'bit.ly'),
            'token' => env('URL_SHORTENER_API_TOKEN'),
        ],

        'firebase' => [
            'driver' => 'firebase',
            'prefix' => env('URL_SHORTENER_PREFIX'),
            'token' => env('URL_SHORTENER_API_TOKEN'),
            'suffix' => env('URL_SHORTENER_STRATEGY', 'UNGUESSABLE'),
        ],

        'is_gd' => [
            'driver' => 'is_gd',
            'base_uri' => 'https://is.gd',
            'statistics' => env('URL_SHORTENER_ANALYTICS', false),
        ],

        'ouo_io' => [
            'driver' => 'ouo_io',
            'token' => env('URL_SHORTENER_API_TOKEN'),
        ],

        'polr' => [
            'driver' => 'polr',
            'prefix' => env('URL_SHORTENER_PREFIX'),
            'token' => env('URL_SHORTENER_API_TOKEN'),
        ],

        'shorte_st' => [
            'driver' => 'shorte_st',
            'token' => env('URL_SHORTENER_API_TOKEN'),
        ],

        'tiny_url' => [
            'driver' => 'tiny_url',
        ],

        'v_gd' => [
            'driver' => 'is_gd',
            'base_uri' => 'https://v.gd',
            'statistics' => env('URL_SHORTENER_ANALYTICS', false),
        ],
    ],
];
<file_sep><?php

namespace LaraCrafts\UrlShortener\Tests\Unit\Http;

use GuzzleHttp\Exception\ClientException;
use LaraCrafts\UrlShortener\Http\BitLyShortener;

class BitLyShortenerTest extends HttpTestCase
{
    /**
     * @var \LaraCrafts\UrlShortener\Http\BitLyShortener
     */
    protected $shortener;

    /**
     * {@inheritDoc}
     */
    protected function setUp(): void
    {
        parent::setUp();

        $this->shortener = new BitLyShortener($this->client, 'API_TOKEN', false);
    }

    /**
     * Test the shortening of URLs through Bit.ly.
     *
     * @return void
     */
    public function testShortening()
    {
        $this->client->queue(require __DIR__ . '/../../Fixtures/bit_ly/shorten.http-200.php');

        $shortenedUrl = $this->shortener->shorten('https://google.com');
        $request = $this->client->getRequest(0);

        $this->assertNotNull($request);
        $this->assertEquals('POST', $request->getMethod());
        $this->assertEquals('api-ssl.bitly.com', $request->getUri()->getHost());
        $this->assertEquals('/v4/shorten', $request->getRequestTarget());
        $this->assertEquals('Bearer API_TOKEN', $request->getHeader('Authorization')[0]);
        $this->assertEquals('application/json', $request->getHeader('Content-Type')[0]);

        $this->assertEquals('https://bit.ly/2WujBe0', $shortenedUrl);
    }

    /**
     * Test failure to authenticate with Bit.ly.
     *
     * @return void
     * @depends testShortening
     */
    public function testUnauthorized()
    {
        $this->client->queue(require __DIR__ . '/../../Fixtures/bit_ly/shorten.http-403.php');

        $this->expectException(ClientException::class);
        $this->shortener->shorten('https://google.com');
    }
}

languages: Markdown PHP

repo_language: Markdown

repo_name: LaraCrafts/laravel-url-shortener


text:
<file_sep>Translate selection
===================

Let you select and translate text fragments.

Install: go to http://goo.gl/3lWSj with Google Chrome.

Softpedia review available: http://goo.gl/Lnaie

Security
--------

Permission Warnings:
* Your browsing history: I need access your tabs to inject little JavaScript and read selected text
* Your data on all websites: I need access your tabs whatever the site you're visiting

More info on Permission Warnings: http://goo.gl/G75ZD

Need more security?
100% Clean. Softpedia guarantees that Translate Selection 1.0 is 100% Clean, which means it does not contain any form of malware, including but not limited to: spyware, viruses, trojans and backdoors - http://goo.gl/RTJRh

Features
--------

* New: Translate on right click (context menus)
* Automatic copy of selected text fragments
* Automatic detection of source text's language
* Set and save preferred languages: you don't have to scroll dozen of languages to find your preferred ones

Note: Automatic selection of the text can not work in the extensions gallery (for example, this page), the extensions gallery cannot be scripted! -- Google said that :-(

Version 1.1.8
-------------

* Fix: new API version
* Russian locale, THANKS TO [apaxuc](http://github.com/apaxuc)

Version 1.1.7
-------------

* Replaced Google Translate API (deprecated) with Microsoft Translator's AJAX interface, to fix the "Suspected Terms of Service Abuse" error
* Improvements on right-click translation: pop-up wait animation and better positioning.

Version 1.1.5
-------------

* Bug fix: removed bug on showing error messages from Google API

Version 1.1.4
-------------

* Now you can translate using context menus: try right-click on selection!
* New icon
* Bug fix: now you can translate very long text fragments.

Version 1.1.3
-------------

* Replaced warning when used in extensions gallery with inline message

Version 1.1.2
-------------

* Added warning when used in extensions gallery (the extensions gallery cannot be scripted)

Version 1.1
-------------

* Fixed: selection of text fragments works only in tabs opened after extension's install
<file_sep>function start() {
	setStrings()
	init();
};
function setStrings(){
	$('#selFrom option:first-child').html(t('detectLanguage'));
	$('#selTo option:first-child').html(t('automatic'));
	$('label[for="from"]').html(t('sourceText'));
	$('label[for="to"]').html(t('translatedText'));
	$('#extdescription').html(t('extDescription'));
	$('#nosave').html(t('noSaveNeeded'));
}

var savePrefs = function savePrefs() {
	var preferred = [];
	for (var i = 0, max = $('select[name=preffrom]').length; i < max; i++) {
		preferred.push($($('select[name=preffrom]').get(i)).val() + '|' + $($('select[name=prefto]').get(i)).val());
	}
	localStorage.setItem('preferred', JSON.stringify(preferred));
	init();
	chrome.extension.getBackgroundPage().start();
};
function init(){
	$('.preferredrow, #fromto option').remove();
	if (localStorage.getItem('from') === null) {
		localStorage.setItem('from', '');
	}

	if (localStorage.getItem('to') === null) {
		localStorage.setItem('to', '');
	}
	if (localStorage.getItem('preferred') === null) {
		localStorage.setItem('preferred', JSON.stringify(["|"+window.navigator.language]));
		window.location.hash = 'options';
	}
	var preferred = JSON.parse(localStorage.getItem('preferred'));
	for (var i = 0, max = preferred.length; i < max; i++) {
		var un;
		createPreferredRow(un, preferred[i]);
		var optString = '';
		var L = JSONSwitch(LANGUAGES);
		optString += preferred[i].split('|')[0] ? L[preferred[i].split('|')[0]] : t('detectLanguage');
		optString += ' » ';
		optString += preferred[i].split('|')[1] ? L[preferred[i].split('|')[1]] : t('detectLanguage');
	}
}
var createPreferredRow = function(event, pair) {
	if (typeof event === 'object') {
		event.preventDefault();
	}
	var spf = $('<select name="preffrom"/>').change(savePrefs);
	var spt = $('<select name="prefto"/>').change(savePrefs);
	$('<option>').attr('value', '').html(t('detectLanguage')).appendTo(spf);
	for (var currlang in LANGUAGES) {
		if(LANGUAGES[currlang]) {
			$('<option>').attr('value', LANGUAGES[currlang]).html(LANGUAGES[currlang] ? currlang : t('detectLanguage')).appendTo(spf);
			$('<option>').attr('value', LANGUAGES[currlang]).html(LANGUAGES[currlang] ? currlang : t('detectLanguage')).appendTo(spt);
		}
	}
	var plusButton = $('<button>').addClass('button ico notxt icon-plus').click(function(event) {
		event.preventDefault();
		createPreferredRow(event, "|");
		savePrefs()
	}).html(t('add')).attr('title',t('add'));
	var minusButton = $('<button>').addClass('button ico notxt icon-minus').click(function(event) {
		event.preventDefault();

		if ($('div.preferredrow', $(this).closest('div').closest('form')).length > 1) {
			$(this).closest('div').remove();
			savePrefs()
		}
	}).html(t('remove')).attr('title',t('remove'));
	$(spf).val(pair.split("|")[0]);
	$(spt).val(pair.split("|")[1]);
	$('<div>').addClass('preferredrow').append(spf).append(spt).append(plusButton).append(minusButton).appendTo($('form'));
}

$(document).ready(function(){
	LANGUAGES = {};
	LOCALE = "";
	chrome.i18n.getAcceptLanguages( function(L) {
		LOCALE = L[0];
		currentLanguages = Microsoft.Translator.GetLanguages();
		languageNames = Microsoft.Translator.getLanguageNames(LOCALE);
		for(var i = 0; i < currentLanguages.length; i++) {
			LANGUAGES[languageNames[i]] = currentLanguages[i];
		}
		start();
	} );
});
<file_sep>if(!window.TranslateSelectionIsActive){
	var createBalloon = function createBalloon(message){
		var rect = window.getSelection().getRangeAt(0).getBoundingClientRect();
		var span = document.createElement('span');
		var msg = document.createElement('span');
		var loader = document.createElement('img');
		var pin = document.createElement('img');
		var close = document.createElement('img');
		close.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAGVJREFUeNqckosJwDAIRI8u4ggZ+UbqKBnBWrCpHKFgHwjxEy8mARJ3tzCGTX+ZGTNUIjCkULlzo3b+Kq6bDCn5cIrPjC0f0p2pSlkvFWykWWajJg90+XOk9tC9a20/XPdrXAIMAGD+sgrlLETIAAAAAElFTkSuQmCC';
		span.appendChild(msg);
		span.appendChild(pin);
		span.appendChild(close);
		msg.appendChild(loader);
		document.body.appendChild(span);
		close.addEventListener('click',function(){
			balloon.close();
		},false);
		/* CSS for span */
		span.style.backgroundAttachment = 'scroll';
		span.style.backgroundClip = 'border-box';
		span.style.backgroundColor = 'transparent';
		span.style.backgroundImage = 'none';
		span.style.backgroundOrigin = 'padding-box';
		span.style.borderRadius = '0';
		span.style.bordeStyle = 'none';
		span.style.color = 'white';
		span.style.cursor = 'auto';
		span.style.display = 'block';
		span.style.fontFamily = 'sans-serif';
		span.style.fontSize = '12px';
		span.style.fontStyle = 'normal';
		span.style.fontVariant = 'normal';
		span.style.fontWeight = 'normal';
		span.style.height = 'auto';
		span.style.left = 'auto';
		span.style.lineHeight = 'normal';
		span.style.margin = '0';
		span.style.padding = '0px';
		span.style.position = 'absolute';
		span.style.right = '3px';
		span.style.top = '3px';
		span.style.width = '12px';
		span.style.zIndex = 'auto';
		span.style.textAlign = 'left';
		span.style.font = 'normal normal 12px sans-serif';
		span.style.zIndex = '100';
		span.style.background = 'rgba(10,10,10,.9)';
		span.style.color = '#FFF';
		span.style.position = 'absolute';
		span.style.width = ((rect.width - 30 > 150) ? rect.width - 30 : 150 ) + 'px';
		span.style.left = (rect.left) + 'px';
		span.style.top = (rect.top + rect.height*0 + window.pageYOffset + 8*1) + 'px';
		span.style.padding = '15px';
		span.style.borderRadius = '9px';
		span.style.whiteSpace = 'pre-wrap';
		/*CSS for loader*/
		loader.src='data:image/png;base64,R0lGODlhEAAQAPQAAAoKCv///w8PD8jIyH9/f/j4+NnZ2TAwMFxcXOjo6IyMjJycnCIiImtraz8/P7i4uKurqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFUCAgjmRpnqUwFGwhKoRgqq2YFMaRGjWA8AbZi<KEY>';
		loader.style.backgroundAttachment = 'scroll';
		loader.style.backgroundClip = 'border-box';
		loader.style.backgroundColor = 'transparent';
		loader.style.backgroundImage = 'none';
		loader.style.backgroundOrigin = 'padding-box';
		loader.style.borderRadius = '0';
		loader.style.bordeStyle = 'none';
		loader.style.color = 'white';
		loader.style.cursor = 'pointer';
		loader.style.display = 'block';
		loader.style.fontFamily = 'sans-serif';
		loader.style.fontSize = '12px';
		loader.style.fontStyle = 'normal';
		loader.style.fontVariant = 'normal';
		loader.style.fontWeight = 'normal';
		loader.style.height = '12px';
		loader.style.left = 'auto';
		loader.style.lineHeight = 'normal';
		loader.style.margin = '0 auto';
		loader.style.padding = '0px';
		loader.style.position = 'static';
		loader.style.width = '12px';
		loader.style.zIndex = 'auto';
		/* CSS for pin */
		pin.style.backgroundAttachment = 'scroll';
		pin.style.backgroundClip = 'border-box';
		pin.style.backgroundColor = 'transparent';
		pin.style.backgroundImage = 'none';
		pin.style.backgroundOrigin = 'padding-box';
		pin.style.border = 'none';
		pin.style.color = 'white';
		pin.style.cursor = 'auto';
		pin.style.display = 'block';
		pin.style.fontFamily = 'sans-serif';
		pin.style.fontSize = '12px';
		pin.style.fontStyle = 'normal';
		pin.style.fontVariant = 'normal';
		pin.style.fontWeight = 'normal';
		pin.style.height = '8px';
		pin.style.left = '30px';
		pin.style.lineHeight = 'normal';
		pin.style.margin = '0px';
		pin.style.outlineColor = 'white';
		pin.style.outlineStyle = 'none';
		pin.style.outlineWidth = '0px';
		pin.style.padding = '0';
		pin.style.position = 'absolute';
		pin.style.right = 'auto';
		pin.style.textAlign = 'left';
		pin.style.top = 'auto';
		pin.style.verticalAlign = 'baseline';
		pin.style.width = '15px';
		pin.style.zIndex = 'auto';
		/* CSS for close */
		close.style.backgroundAttachment = 'scroll';
		close.style.backgroundClip = 'border-box';
		close.style.backgroundColor = 'transparent';
		close.style.backgroundImage = 'none';
		close.style.backgroundOrigin = 'padding-box';
		close.style.border = 'none';
		close.style.color = 'white';
		close.style.cursor = 'pointer';
		close.style.display = 'block';
		close.style.fontFamily = 'sans-serif';
		close.style.fontSize = '12px';
		close.style.fontStyle = 'normal';
		close.style.fontVariant = 'normal';
		close.style.fontWeight = 'normal';
		close.style.height = '12px';
		close.style.left = 'auto';
		close.style.lineHeight = 'normal';
		close.style.margin = '0px';
		close.style.padding = '0px';
		close.style.position = 'absolute';
		close.style.right = '3px';
		close.style.textAlign = 'left';
		close.style.top = '3px';
		close.style.width = '12px';
		close.style.zIndex = 'auto';
		/*
		* public object
		*/
		var balloon = {
			getBalloon : function() {
				return span;
			},
			placeTop : function(){
				span.style.marginTop = '-'+(span.clientHeight+20)+'px';
				pin.src = 'data:image/png;base64,iVB<KEY>';
				pin.style.bottom = '-8px';
				pin.style.top = 'auto';
				if(span.offsetTop < 0) this.placeBorder();
			},
			placeBorder : function(){
				pin.src = 'data:image/png;base64,iVBORw<KEY>';
				pin.style.top = '-8px';
				pin.style.bottom = 'auto';
				span.style.marginTop = 0;
			},
			setText : function(txt){
				txt = txt.replace(/^"|"$/gi,'').replace(/\\u00([0-9A-F]{2})/gi,"&#x$1;");
				msg.innerHTML = txt;
			},
			close : function(){
				span.parentNode.removeChild(span);
			}
		};
		return balloon;
	}
	chrome.extension.onRequest.addListener(
		function (req, sender,sendResponse) {
			if(req.method == 'get') {
				var str = window.getSelection().toString();
				if(str !== '') {
					sendResponse({'text' : str});
				}
			}
			if(req.method == 'prepareBalloon') {
				balloon = createBalloon('...');
				balloon.placeTop();
				sendResponse({});
			}
			if(req.method == 'getContextMenus') {
				balloon.setText(req.string);
				balloon.placeTop();
				sendResponse({});
			}
		}
	);

}
window.TranslateSelectionIsActive = true;<file_sep>function contextMenusOnClick(info,tab,opt) {
	var balloon;
	chrome.tabs.getSelected(null, function(tab) { // get selected string in current tab
		chrome.tabs.executeScript(tab.id,{file:'js/content.js',allFrames:true},function() {
			chrome.tabs.getSelected(null, function(tab) { // get selected string in current tab
				chrome.tabs.sendRequest(tab.id,{'method':'prepareBalloon'},function(){
					var F = info.selectionText;
					$.ajax({
						url : 'http://api.microsofttranslator.com/V2/Ajax.svc/Translate',
						data : {
							'appId'       : '<KEY>',
							'text'        : F,
							'from'        : opt.split("|")[0],
							'to'          : opt.split("|")[1],
							'contentType' : 'text/plain'
						},
						'success' : function(T)  {
							T = T.replace(/^"|"$/gi,'');
							chrome.tabs.getSelected(null, function(tab) { // get selected string in current tab
								chrome.tabs.executeScript(tab.id,{file:'js/content.js',allFrames:true},function() {injCallBack(T)});
							});
						},
						'error' : function(jqXHR, textStatus, errorThrown) {
							var T = 'ERROR! ' + textStatus;
							chrome.tabs.getSelected(null, function(tab) { // get selected string in current tab
								chrome.tabs.executeScript(tab.id,{file:'js/content.js',allFrames:true},function() {injCallBack(T)});
							});
						}
					});
				})
			});
		});
	});
}

var injCallBack = function(S){
	chrome.tabs.getSelected(null, function(tab) { // get selected string in current tab
		chrome.tabs.sendRequest(tab.id,{'method':'getContextMenus','string':S}, getRequestResponseCallback)
	});
}
var getRequestResponseCallback = function getRequestResponseCallback(response) {
	/*
	* TODO
	*/
};

function createcontextMenusOption(opt){
	var optString = '';
	var L = JSONSwitch(LANGUAGES);
	optString += opt.split('|')[0] ? L[opt.split('|')[0]] : t('detectLanguage');
	optString += ' » ';
	optString += opt.split('|')[1] ? L[opt.split('|')[1]] : t('detectLanguage');
	chrome.contextMenus.create({
		"title": optString,
		"contexts":['selection'],
		"onclick": function(opt){
			return function(info,tab) {
				contextMenusOnClick(info,tab,opt)
			}
		}(opt)
	});
}

function start() {
	if(localStorage.getItem('version') === null) {
		localStorage.setItem('version','0');
	}
	if(localStorage.getItem('version') !== null && localStorage.getItem('version') !== '1.1.8.3'){
		window.open('info.html');
		localStorage.setItem('version','1.1.8.3');
	}
	if (localStorage.getItem('from') === null) {
		localStorage.setItem('from', '');
	}

	if (localStorage.getItem('to') === null) {
		localStorage.setItem('to', '');
	}
	if (localStorage.getItem('preferred') === null) {
		localStorage.setItem('preferred', JSON.stringify(["|"+window.navigator.language]));
		window.open('options.html');
	}
	var preferred = JSON.parse(localStorage.getItem('preferred'));
	chrome.contextMenus.removeAll();
	for (var i = 0, max = preferred.length; i < max; i++) {
		createcontextMenusOption(preferred[i]);
	}
}

$(document).ready(function(){
	LANGUAGES = {};
	LOCALE = "";
	chrome.i18n.getAcceptLanguages( function(L) {
		LOCALE = L[0];
		currentLanguages = Microsoft.Translator.GetLanguages();
		languageNames = Microsoft.Translator.getLanguageNames(LOCALE);
		for(var i = 0; i < currentLanguages.length; i++) {
			LANGUAGES[languageNames[i]] = currentLanguages[i];
		}
		start();
	} );
});

branch_name: refs/heads/master

languages: Markdown JavaScript

repo_name: giatro/TranslateSelection