code:
<?php

namespace Embed\Http;

/**
 * Class to split and manipulate urls.
 */
class Url
{
    private $info;
    private $url;
    private static $public_suffix_list;

    /**
     * Create a new Url instance.
     *
     * @param string $url
     *
     * @return Url
     */
    public static function create($url)
    {
        return Redirects::resolve(new static($url));
    }

    /**
     * Constructor. Sets the url.
     *
     * @param string $url
     */
    private function __construct($url)
    {
        $this->parseUrl($url);
    }

    /**
     * Returns the url.
     *
     * @return string
     */
    public function __toString()
    {
        if ($this->url === null) {
            return $this->url = $this->buildUrl();
        }

        return $this->url;
    }

    /**
     * Remove the built url on clone.
     */
    public function __clone()
    {
        $this->url = null;
    }

    /**
     * Check if the url match with a specific pattern. The patterns only accepts * and ?
     *
     * @param string|array $patterns The pattern or an array with various patterns
     *
     * @return bool
     */
    public function match($patterns)
    {
        if (!is_array($patterns)) {
            $patterns = [$patterns];
        }

        //Remove scheme and query
        $long_url = preg_replace('|^(\w+://)|', '', (string) $this);
        $short_url = preg_replace('|(\?.*)?$|', '', $long_url);

        foreach ($patterns as $pattern) {
            if ($pattern[0] === '?') { // ?hello=world => *?hello=world
                $pattern = '*' . $pattern;
            }

            $pattern = str_replace('\\*', '.*', preg_quote($pattern, '|'));

            if (strpos($pattern, '?') === false) {
                $url = $short_url;
            } else {
                $pattern = str_replace('\\?&', '[\\?&]', $pattern);
                $url = $long_url;
            }

            if (preg_match('|^'.$pattern.'$|i', $url)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Return the content of the url (for embedded images).
     *
     * @return string The content or null
     */
    public function getContent()
    {
        return isset($this->info['content']) ? $this->info['content'] : null;
    }

    /**
     * Return the extension of the url (html, php, jpg, etc).
     *
     * @return string The scheme or null
     */
    public function getExtension()
    {
        return isset($this->info['extension']) ? $this->info['extension'] : null;
    }

    /**
     * Returns a new instance with other relative url.
     *
     * @param string $url
     *
     * @return Url
     */
    public function createAbsolute($url)
    {
        return self::create($this->getAbsolute($url));
    }

    /**
     * Returns a clone with other extension.
     *
     * @param string $extension
     *
     * @return Url
     */
    public function withExtension($extension)
    {
        $clone = clone $this;
        $clone->info['extension'] = $extension;

        if (empty($clone->info['file'])) {
            $clone->info['file'] = array_pop($clone->info['path']);
        }

        return $clone;
    }

    /**
     * Return the scheme of the url (for example http, https, ftp, etc).
     *
     * @return string The scheme or null
     */
    public function getScheme()
    {
        return isset($this->info['scheme']) ? $this->info['scheme'] : null;
    }

    /**
     * Returns a clone with other scheme.
     *
     * @param string $scheme
     *
     * @return Url
     */
    public function withScheme($scheme)
    {
        $clone = clone $this;
        $clone->info['scheme'] = $scheme;

        return $clone;
    }

    /**
     * Return the host of the url (for example: google.com).
     *
     * @return string The host or null
     */
    public function getHost()
    {
        return isset($this->info['host']) ? $this->info['host'] : null;
    }

    /**
     * Returns a clone with other host.
     *
     * @param string $host
     *
     * @return Url
     */
    public function withHost($host)
    {
        $clone = clone $this;
        $clone->info['host'] = $host;

        return $clone;
    }

    /**
     * Return the domain of the url (for example: google).
     *
     * @param bool $first_level True to return the first level domain (.com, .es, etc)
     *
     * @return string
     */
    public function getDomain($first_level = false)
    {
        $host = $this->getHost();

        if (empty($host)) {
            return '';
        }

        $host = array_reverse(explode('.', $host));

        switch (count($host)) {
            case 1:
                return $host[0];

            case 2:
                return $first_level ? ($host[1].'.'.$host[0]) : $host[1];

            default:
                $tld = $host[1].'.'.$host[0];
                $suffixes = self::getSuffixes();

                if (in_array($tld, $suffixes, true)) {
                    return $first_level ? $host[2].'.'.$tld : $host[2];
                }

                return $first_level ? $host[1].'.'.$host[0] : $host[1];
        }
    }

    /**
     * Return a specific directory position in the path of the url.
     *
     * @param int $position The position of the directory (0 based index)
     *
     * @return string|null
     */
    public function getDirectoryPosition($position)
    {
        if ($position === count($this->info['path'])) {
            return urldecode($this->info['file']);
        }

        return isset($this->info['path'][$position]) ? urldecode($this->info['path'][$position]) : null;
    }

    /**
     * Returns a clone with other directory in a specific position.
     *
     * @param int|null $key   The position of the subdirectory (0 based index)
     * @param string   $value The new value
     *
     * @return Url
     */
    public function withDirectoryPosition($key, $value)
    {
        $clone = clone $this;

        $pos = $clone->info['path'];
        $hasFile = isset($clone->info['file']);

        if ($hasFile) {
            $pos[] = $clone->info['file'];
        }

        $pos[$key] = $value;

        if ($hasFile) {
            $clone->info['file'] = array_pop($pos);
        }

        $clone->info['path'] = $pos;

        return $clone;
    }

    /**
     * Return all directories.
     *
     * @return string
     */
    public function getDirectories()
    {
        return !empty($this->info['path']) ? '/'.implode('/', $this->info['path']).'/' : '/';
    }

    /**
     * Slice path.
     *
     * @param int      $offset
     * @param int|null $length
     *
     * @return array
     */
    public function getSlicePath($offset, $length = null)
    {
        $array = explode('/', $this->getPath());

        if (empty($array[0])) {
            array_shift($array);
        }

        return array_slice($array, $offset, $length);
    }

    /**
     * Return the url path.
     *
     * @return string
     */
    public function getPath()
    {
        $path = !empty($this->info['path']) ? '/'.implode('/', array_map('self::urlEncode', $this->info['path'])).'/' : '/';

        if (isset($this->info['file'])) {
            $path .= self::urlEncode($this->info['file']);

            if (isset($this->info['extension'])) {
                $path .= '.'.$this->info['extension'];
            }
        }

        return $path;
    }

    /**
     * Returns a clone with other path.
     *
     * @param string $path
     *
     * @return Url
     */
    public function withPath($path)
    {
        $clone = clone $this;

        $clone->setPath($path);

        return $clone;
    }

    /**
     * Returns a clone with path appended.
     *
     * @param string $path
     *
     * @return Url
     */
    public function withAddedPath($path)
    {
        $path = $this->getPath().'/'.$path;

        return $this->withPath($path);
    }

    /**
     * Check if the url has a query parameter.
     *
     * @param string $name
     *
     * @return bool
     */
    public function hasQueryParameter($name)
    {
        return isset($this->info['query'][$name]);
    }

    /**
     * Returns all query parameters.
     *
     * @return array
     */
    public function getQueryParameters()
    {
        return $this->info['query'];
    }

    /**
     * Returns a query parameter value.
     *
     * @param string $name
     *
     * @return string|null
     */
    public function getQueryParameter($name)
    {
        return isset($this->info['query'][$name]) ? $this->info['query'][$name] : null;
    }

    /**
     * Returns a clone with a new query parameter.
     *
     * @param string $name  The parameter name
     * @param string $value The parameter value
     *
     * @return Url
     */
    public function withQueryParameter($name, $value)
    {
        $clone = clone $this;

        $clone->info['query'][$name] = $value;

        return $clone;
    }

    /**
     * Returns a clone with new query parameters merged.
     *
     * @param array $parameters
     *
     * @return Url
     */
    public function withAddedQueryParameters(array $parameters)
    {
        $clone = clone $this;

        $clone->info['query'] = empty($clone->info['query']) ? $parameters : array_replace($clone->info['query'], $parameters);

        return $clone;
    }

    /**
     * Returns a clone with new query parameters.
     *
     * @param array $parameters
     *
     * @return Url
     */
    public function withQueryParameters(array $parameters)
    {
        $clone = clone $this;

        $clone->info['query'] = $parameters;

        return $clone;
    }

    /**
     * Return the url fragment.
     *
     * @return string
     */
    public function getFragment()
    {
        return isset($this->info['fragment']) ? $this->info['fragment'] : null;
    }

    /**
     * Return ClassName for domain.
     *
     * Domains started with numbers will get N prepended to their class name.
     *
     * @return string
     */
    public function getClassNameForDomain()
    {
        $className = str_replace(array('-', ' '), '', ucwords(strtolower($this->getDomain())));

        if (is_numeric(mb_substr($className, 0, 1))) {
            $className = 'N'.$className;
        }

        return $className;
    }

    /**
     * Build the url using the splitted data.
     */
    protected function buildUrl()
    {
        $url = '';

        if (isset($this->info['content'])) {
            return 'data:'.$this->info['content'];
        }

        if (isset($this->info['scheme'])) {
            $url .= $this->info['scheme'].'://';
        }

        $user = isset($this->info['user']) ? $this->info['user'] : '';
        $pass = isset($this->info['pass']) ? ':'.$this->info['pass'] : '';
        if ($user || $pass) {
            $url .= $user.$pass.'@';
        }

        if (isset($this->info['host'])) {
            $url .= $this->info['host'];
        }

        if (isset($this->info['port'])) {
            $url .= ':'.$this->info['port'];
        }

        $url .= $this->getPath();

        if (!empty($this->info['query'])) {
            $url .= '?'.rtrim(http_build_query($this->info['query']), '=');
        }
        if (isset($this->info['fragment'])) {
            $url .= '#'.$this->info['fragment'];
        }

        return $url;
    }

    /**
     * Parse an url and split into different pieces.
     *
     * @param string $url The url to parse
     */
    protected function parseUrl($url)
    {
        $this->info = self::parse($url);

        if (isset($this->info['path'])) {
            $this->setPath($this->info['path']);
        }

        if (empty($this->info['query'])) {
            $this->info['query'] = [];

            return;
        }

        // Fix dots and other characters used in query's variables names
        // https://github.com/oscarotero/Embed/issues/101
        $queryString = preg_replace_callback('/(^|(?<=&))[^=[&]+/', function ($key) {
            return bin2hex(urldecode($key[0]));
        }, $this->info['query']);

        parse_str($queryString, $query);

        $this->info['query'] = [];

        foreach ((array) $query as $key => $value) {
            $this->info['query'][hex2bin($key)] = $value;
        }
    }

    /**
     * Return an absolute url based in a relative.
     *
     * @return string
     */
    public function getAbsolute($url)
    {
        $url = trim($url);

        if (empty($url)) {
            return '';
        }

        if (preg_match('|^\w+:[^/]|', $url)) {
            return $url;
        }

        if (preg_match('|^\w+://|', $url)) {
            return self::validUrlOrEmpty($url);
        }

        if (strpos($url, '://') === 0) {
            return self::validUrlOrEmpty($this->getScheme().$url);
        }

        if (strpos($url, '//') === 0) {
            return self::validUrlOrEmpty($this->getScheme().":$url");
        }

        if ($url[0] === '/') {
            return self::validUrlOrEmpty($this->getScheme().'://'.$this->getHost().$url);
        }

        return self::validUrlOrEmpty($this->getScheme().'://'.$this->getHost().$this->getDirectories().$url);
    }

    /**
     * Parses and adds path and file value.
     *
     * @param string $path
     */
    private function setPath($path)
    {
        $this->info['path'] = $this->info['file'] = $this->info['extension'] = $this->info['content'] = null;

        if ($this->getScheme() === 'data') {
            $this->info['content'] = $path;

            return;
        }

        $file = substr(strrchr($path, '/'), 1);

        if (strlen($file)) {
            $path = substr($path, 0, -strlen($file));

            if (preg_match('/(.*)\.([\w]+)$/', $file, $match)) {
                $this->info['file'] = rawurldecode($match[1]);
                $this->info['extension'] = $match[2];
            } else {
                $this->info['file'] = rawurldecode($file);
            }
        }

        $this->info['path'] = [];

        foreach (explode('/', $path) as $dir) {
            $dir = rawurldecode($dir);

            if ($dir !== '') {
                $this->info['path'][] = $dir;
            }
        }
    }

    private static function getSuffixes()
    {
        if (self::$public_suffix_list === null) {
            self::$public_suffix_list = (@include __DIR__.'/../resources/public_suffix_list.php') ?: [];
        }

        return self::$public_suffix_list;
    }

    /**
     * UTF-8 compatible parse_url
     * http://php.net/manual/en/function.parse-url.php#114817
     *
     * @param string $url
     *
     * @return string
     */
    private static function parse($url)
    {
        $enc_url = preg_replace_callback(
            '%[^:/@?&=#]+%usD',
            function ($matches) {
                return rawurlencode($matches[0]);
            },
            $url
        );

        $parts = parse_url($enc_url);

        if ($parts === false) {
            throw new \InvalidArgumentException('Malformed URL: ' . $url);
        }

        if (!empty($parts['scheme']) && !in_array($parts['scheme'], ['http', 'https', 'data'])) {
            throw new \InvalidArgumentException(sprintf('Invalid URL scheme: "%s"', $parts['scheme']));
        }

        foreach ($parts as $name => $value) {
            $parts[$name] = rawurldecode($value);
        }
        return $parts;
    }

    private static function urlEncode($path)
    {
        // : - used for files
        // @, + and , - used for GoogleMaps adapter url (in view and streetview modes)
        return str_replace(['%3A','%40','%2C', '%2B'], [':','@',',', '+'], rawurlencode($path));
    }

    private static function validUrlOrEmpty($url)
    {
        return parse_url($url) === false ? '' : $url;
    }
}


code:
<?php

namespace Embed\Providers\Api;

use Embed\Adapters\Adapter;
use Embed\Providers\Provider;

/**
 * Provider to use the API of archive.org.
 */
class Archive extends Provider
{
    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        $endPoint = $adapter->getResponse()->getUrl()->withQueryParameter('output', 'json');
        $response = $adapter->getDispatcher()->dispatch($endPoint);

        if (($json = $response->getJsonContent())) {
            $this->bag->set($json);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
        return $this->bag->get('metadata[title][0]');
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription()
    {
        return $this->bag->get('metadata[description][0]');
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        switch ($this->bag->get('metadata[mediatype][0]')) {
            case 'movies':
                return 'video';

            case 'audio':
                return 'audio';

            case 'texts':
                return 'rich';
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getProviderName()
    {
        return 'Internet Archive';
    }

    /**
     * {@inheritdoc}
     */
    public function getUrl()
    {
        return $this->normalizeUrl($this->bag->get('url'));
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorName()
    {
        $this->bag->get('metadata[creator][0]');
    }

    /**
     * {@inheritdoc}
     */
    public function getImagesUrls()
    {
        $images = (array) $this->bag->get('misc[image]');

        foreach (array_keys((array) $this->bag->get('files')) as $url) {
            $images[] = $url;
        }

        return $this->normalizeUrls($images);
    }
}

repo_name: ksuudilla/gnu

license: mit


code:
<?php

namespace Embed\Providers\Api;

use Embed\Adapters\Adapter;
use Embed\Providers\Provider;
use Embed\Http\Url;

/**
 * Provider to use the open graph API of facebook.
 */
class Facebook extends Provider
{
    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        if (($endPoint = $this->getEndPoint())) {
            $response = $adapter->getDispatcher()->dispatch($endPoint);

            if ($json = $response->getJsonContent()) {
                $this->bag->set($json);
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
        return $this->bag->get('name');
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription()
    {
        return $this->bag->get('description') ?: $this->bag->get('about');
    }

    /**
     * {@inheritdoc}
     */
    public function getProviderName()
    {
        return 'Facebook';
    }

    /**
     * {@inheritdoc}
     */
    public function getUrl()
    {
        return $this->normalizeUrl($this->bag->get('url'));
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorName()
    {
        return $this->bag->get('username') ?: $this->bag->get('owner[name]');
    }

    /**
     * {@inheritdoc}
     */
    public function getImagesUrls()
    {
        $images = (array) $this->bag->get('cover[source]');

        if (($id = $this->bag->get('id'))) {
            $images[] = 'https://graph.facebook.com/'.$id.'/picture';
        }

        return $this->normalizeUrls($images);
    }

    /**
     * {@inheritdoc}
     */
    public function getPublishedTime()
    {
        return $this->bag->get('start_time');
    }

    /**
     * {@inheritdoc}
     */
    public function getCode()
    {
        return $this->bag->get('embed_html', true);
    }

    /**
     * Returns the Graph API Endpoint
     *
     * @return Url|null
     */
    private function getEndPoint()
    {
        $url = $this->adapter->getResponse()->getUrl();
        $key = $this->adapter->getConfig('facebook[key]');

        if (empty($key)) {
            return;
        }

        if ($url->getDirectoryPosition(0) === 'events') {
            return  Url::create('https://graph.facebook.com/')
                        ->withAddedPath($url->getDirectoryPosition(1))
                        ->withQueryParameter('access_token', $key)
                        ->withQueryParameter('fields', $this->adapter->getConfig('facebook[events_fields]'));
        }

        if ($url->getDirectoryPosition(1) === 'videos') {
            return  Url::create('https://graph.facebook.com/')
                        ->withAddedPath($url->getDirectoryPosition(2))
                        ->withQueryParameter('access_token', $key)
                        ->withQueryParameter('fields', $this->adapter->getConfig('facebook[videos_fields]'));
        }
    }
}


code:
<?php

namespace Embed\Providers\Api;

use Embed\Adapters\Adapter;
use Embed\Providers\Provider;

/**
 * Provider to use the API of gist.github.com.
 */
class Gist extends Provider
{
    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        $endPoint = $adapter->getResponse()->getUrl()->withExtension('json');
        $response = $adapter->getDispatcher()->dispatch($endPoint);

        if (($json = $response->getJsonContent())) {
            $this->bag->set($json);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription()
    {
        return $this->bag->get('description');
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        if ($this->getCode() !== null) {
            return 'rich';
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorName()
    {
        return $this->bag->get('owner');
    }

    /**
     * {@inheritdoc}
     */
    public function getPublishedTime()
    {
        return $this->bag->get('created_at');
    }

    /**
     * {@inheritdoc}
     */
    public function getCode()
    {
        if (($code = $this->bag->get('div', true)) && ($stylesheet = $this->normalizeUrl($this->bag->get('stylesheet')))) {
            return  '<link href="'.$stylesheet.'" rel="stylesheet">'.$code;
        }
    }
}


code:
<?php

namespace Embed\Providers\Api;

use Embed\Adapters\Adapter;
use Embed\Providers\Provider;
use Embed\Http\Url;
use Embed\Utils;

/**
 * Provider to use the API of google.com
 */
class GoogleMaps extends Provider
{
    protected $mode;
    protected $config = [
        'key' => null,
    ];

    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        $mode = $adapter->getResponse()->getUrl()->getDirectoryPosition(1);

        // Default is view (if mode is not mentioned in the url)
        $this->mode = 'view';

        switch ($mode) {
            case 'place':
            case 'dir':
            case 'search':
                $this->mode = $mode;
                break;
        }

        // check streetview mode
        // simple check,- starts with @, ends with t
        if ((substr($mode, 0, 1) === '@') &&  (substr($mode, -1) === 't')) {
            $this->mode = 'streetview';
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
        $url = $this->adapter->getResponse()->getUrl();

        if ($this->mode === 'place') {
            return $url->getDirectoryPosition(2);
        }

        if ($this->mode === 'dir') {
            return $url->getDirectoryPosition(2).' / '.$url->getDirectoryPosition(3);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getProviderName()
    {
        return 'Google Maps';
    }

    /**
     * {@inheritdoc}
     */
    public function getCode()
    {
        $url = $this->adapter->getResponse()->getUrl();
        $key = $this->adapter->getConfig('google[key]');

        if (empty($key)) {
            return;
        }

        switch ($this->mode) {
            case 'view':
                $pos = self::getPosition($this->mode, $url);

                return Utils::iframe($url
                    ->withPath('maps/embed/v1/'.$this->mode)
                    ->withQueryParameters([
                        'center' => $pos['coordinates'],
                        'zoom' => $pos['zoom'],
                        'key' => $key,
                    ]));

            case 'streetview':
                $pos = self::getPosition($this->mode, $url);

                return Utils::iframe($url
                    ->withPath('maps/embed/v1/'.$this->mode)
                    ->withQueryParameters([
                        'location' => $pos['coordinates'],
                        'heading' => $pos['heading'],
                        'pitch' =>  $pos['pitch'],
                        'fov' =>  $pos['fov'],
                        'key' => $key,
                    ]));

            case 'place':
            case 'search':
                return Utils::iframe($url
                    ->withPath('maps/embed/v1/'.$this->mode)
                    ->withQueryParameters([
                        'q' => $url->getDirectoryPosition(2),
                        'key' => $key,
                    ]));

            case 'dir':
                return Utils::iframe($url
                    ->withPath('maps/embed/v1/directions')
                    ->withQueryParameters([
                        'origin' => $url->getDirectoryPosition(2),
                        'destination' => $url->getDirectoryPosition(3),
                        'key' => $key,
                    ]));
        }
    }

    /**
     * Returns parsed position data from url.
     *
     * @param  string $mode The url mode
     * @param  Url    $url
     *
     * @return array
     */
    private static function getPosition($mode, Url $url)
    {
        // Set defaults
        $position = [
            'coordinates' => '',
            'zoom' => '4',
            'heading' => '0',
            'pitch' => '0',
            'fov' => '90'
        ];

        if ($mode === 'view') {
            $pos = explode(",", $url->getDirectoryPosition(1));
            $position['coordinates'] = str_replace('@', '', $pos[0]).','.$pos[1];
            $position['zoom'] = str_replace('z', "", $pos[2]);
        }

        if ($mode === 'streetview') {
            $pos = explode(",", $url->getDirectoryPosition(1));
            $position['coordinates'] = str_replace('@', '', $pos[0]).','.$pos[1];
            $position['zoom'] = str_replace('a', '', $pos[2]); // seems not used by google (emulated by other params)
            $position['heading'] = str_replace('h', '', $pos[4]);
            $position['fov'] = str_replace('y', '', $pos[3]);
            $pitch = str_replace('t', '', $pos[5]); // t is pitch but in 180% format
            if (is_numeric($pitch)) {
                $position['pitch'] = floatval($pitch) - 90;
            }
        }

        return $position;
    }
}


code:
<?php

namespace Embed\Providers\Api;

use Embed\Http\Url;
use Embed\Adapters\Adapter;
use Embed\Providers\Provider;

/**
 * Provider to use the API of imageshack.com.
 */
class Imageshack extends Provider
{
    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        $id = $adapter->getResponse()->getUrl()->getDirectoryPosition(1);
        $endPoint = Url::create('https://api.imageshack.com/v2/images/'.$id);
        $response = $adapter->getDispatcher()->dispatch($endPoint);

        if (($json = $response->getJsonContent()) && !empty($json['result'])) {
            $this->bag->set($json['result']);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
        return $this->bag->get('title');
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription()
    {
        return $this->bag->get('description');
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        return 'photo';
    }

    /**
     * {@inheritdoc}
     */
    public function getPublishedTime()
    {
        return $this->bag->get('creation_date');
    }

    /**
     * {@inheritdoc}
     */
    public function getWidth()
    {
        return $this->bag->get('width');
    }

    /**
     * {@inheritdoc}
     */
    public function getHeight()
    {
        return $this->bag->get('height');
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorName()
    {
        return $this->bag->get('owner[username]');
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorUrl()
    {
        $username = $this->getAuthorName();

        if (!empty($username)) {
            return 'http://imageshack.com/user/'.$username;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getImagesUrls()
    {
        return $this->normalizeUrls($this->bag->get('direct_link'));
    }
}


code:
<?php

namespace Embed\Providers\Api;

use Embed\Http\Url;
use Embed\Adapters\Adapter;
use Embed\Providers\Provider;

/**
 * Provider to use the API of soundcloud.
 */
class Soundcloud extends Provider
{
    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        $key = $adapter->getConfig('soundcloud[key]');

        if (!empty($key)) {
            $endPoint = Url::create('http://api.soundcloud.com/resolve.json')
                ->withQueryParameters([
                    'client_id' => $key,
                    'url' => (string) $adapter->getResponse()->getUrl(),
                ]);

            $response = $adapter->getDispatcher()->dispatch($endPoint);

            if ($json = $response->getJsonContent()) {
                $this->bag->set($json);
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
        return $this->bag->get('title');
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription()
    {
        return $this->bag->get('description');
    }

    /**
     * {@inheritdoc}
     */
    public function getUrl()
    {
        return $this->normalizeUrl($this->bag->get('permalink_url'));
    }

    /**
     * {@inheritdoc}
     */
    public function getImagesUrls()
    {
        $images = [];

        if (empty($this->bag->get('artwork_url')) && ($img = $this->bag->get('user[avatar_url]'))) {
            $images[] = str_replace('-large.jpg', '-t500x500.jpg', $img);
        }

        return $this->normalizeUrls($images);
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorName()
    {
        return $this->bag->get('user[username]');
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorUrl()
    {
        return $this->normalizeUrl($this->bag->get('user[permalink_url]'));
    }
}


code:
<?php

namespace Embed\Providers\Api;

use Embed\Adapters\Adapter;
use Embed\Providers\Provider;

/**
 * Provider to use the API of wikipedia.
 */
class Wikipedia extends Provider
{
    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        $titles = $adapter->getResponse()->getUrl()->getDirectoryPosition(1);

        if (!empty($titles)) {
            //extract images
            $endPoint = $adapter->getResponse()->getUrl()
                ->withPath('/w/api.php')
                ->withQueryParameters([
                    'action' => 'query',
                    'format' => 'json',
                    'continue' => '',
                    'titles' => $titles,
                    'prop' => 'images',
                ]);

            $response = $adapter->getDispatcher()->dispatch($endPoint);

            if (($json = $response->getJsonContent())) {
                $this->bag->set('images', $json);
            }

            //extract content
            $endPoint = $endPoint
                ->withQueryParameter('prop', 'extracts')
                ->withQueryParameter('exchars', 1500);

            $response = $adapter->getDispatcher()->dispatch($endPoint);

            if (($json = $response->getJsonContent())) {
                $this->bag->set('extracts', $json);
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
        $pages = $this->bag->get('extracts[query][pages]');

        if (!empty($pages)) {
            $page = current($pages);

            return strip_tags($page['title']);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription()
    {
        $pages = $this->bag->get('extracts[query][pages]');

        if (!empty($pages)) {
            return $this->bag->get('extracts[query][pages]['.key($pages).'][extract]');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getImagesUrls()
    {
        $images = [];

        $pages = $this->bag->get('images[query][pages]');

        if (!empty($pages)) {
            $page = current($pages);

            $imgs = [];

            if (isset($page['images'])) {
                foreach ($page['images'] as $image) {
                    switch (strrchr($image['title'], '.')) {
                        case '.png':
                        case '.jpg':
                        case '.gif':
                        case '.jpeg':
                            $imgs[] = $image['title'];
                            break;
                    }
                }
            }

            //Get image urls
            if (!empty($imgs)) {
                $endPoint = $this->adapter->getResponse()->getUrl()
                    ->withPath('/w/api.php')
                    ->withQueryParameters([
                        'action' => 'query',
                        'prop' => 'imageinfo',
                        'iiprop' => 'url',
                        'format' => 'json',
                        'continue' => '',
                        'titles' => implode('|', $imgs),
                    ]);

                $response = $this->adapter->getDispatcher()->dispatch($endPoint);
                $json = $response->getJsonContent();

                if (isset($json['query']['pages'])) {
                    foreach ($json['query']['pages'] as $page) {
                        if (isset($page['imageinfo'][0]['url'])) {
                            $images[] = $page['imageinfo'][0]['url'];
                        }
                    }
                }
            }
        }

        return $this->normalizeUrls($images);
    }
}


code:
<?php

namespace Embed\Providers;

use Embed\Adapters\Adapter;

/**
 * Provider to get the data from the Dublin Core data elements in the HTML
 */
class Dcterms extends Provider
{
    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        if (!($html = $adapter->getResponse()->getHtmlContent())) {
            return;
        }

        foreach ($html->getElementsByTagName('meta') as $meta) {
            $name = trim(strtolower($meta->getAttribute('name')));
            $value = $meta->getAttribute('content');

            if (empty($name) || empty($value)) {
                continue;
            }

            foreach (['dc.', 'dc:', 'dcterms:'] as $prefix) {
                if (stripos($name, $prefix) === 0) {
                    $name = substr($name, strlen($prefix));
                    $this->bag->set($name, $value);
                }
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
        return $this->bag->get('title');
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription()
    {
        return $this->bag->get('description');
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorName()
    {
        return $this->bag->get('creator') ?: $this->bag->get('author');
    }

    /**
     * {@inheritdoc}
     */
    public function getPublishedTime()
    {
        foreach (['date', 'date.created', 'date.issued'] as $key) {
            if ($found = $this->bag->get($key)) {
                return $found;
            }
        }
    }
}


code:
<?php

namespace Embed\Providers;

use Embed\Utils;
use Embed\Adapters\Adapter;
use Embed\Http\Url;
use DOMDocument;
use Exception;

/**
 * Provider to get the data from the HTML code
 */
class Html extends Provider
{
    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        if (!($html = $adapter->getResponse()->getHtmlContent())) {
            return;
        }

        $this->extractLinks($html);
        $this->extractMetas($html);
        $this->extractImages($html);

        //Title
        $title = $html->getElementsByTagName('title');

        if ($title->length) {
            $this->bag->set('title', $title->item(0)->nodeValue);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
        return $this->bag->get('title');
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription()
    {
        return $this->bag->get('description');
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        return $this->bag->has('video_src') ? 'video' : null;
    }

    /**
     * {@inheritdoc}
     */
    public function getTags()
    {
        $keywords = $this->bag->get('keywords').','.$this->bag->get('news_keywords');

        return array_filter(
            array_map('trim', explode(',', $keywords)),
            function ($value) {
                return !empty($value) && substr($value, -3) !== '...';
            }
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getFeeds()
    {
        return $this->normalizeUrls($this->bag->get('feeds'));
    }

    /**
     * {@inheritdoc}
     */
    public function getCode()
    {
        $src = $this->normalizeUrl($this->bag->get('video_src'));

        if ($src !== null) {
            switch ($this->bag->get('video_type')) {
                case 'application/x-shockwave-flash':
                    return Utils::flash($src, $this->getWidth(), $this->getHeight());
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getUrl()
    {
        return $this->normalizeUrl($this->bag->get('canonical'));
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorName()
    {
        return $this->bag->get('author') ?: $this->bag->get('article:author') ?: $this->bag->get('contributors');
    }

    /**
     * {@inheritdoc}
     */
    public function getProviderIconsUrls()
    {
        return $this->normalizeUrls($this->bag->get('icons'));
    }

    /**
     * {@inheritdoc}
     */
    public function getImagesUrls()
    {
        $images = $this->normalizeUrls($this->bag->get('images'));

        if (!empty($images)) {
            $maxImages = $this->adapter->getConfig('html[max_images]', -1);

            if ($maxImages > -1) {
                return array_slice($images, 0, $maxImages);
            }
        }

        return $images;
    }

    /**
     * {@inheritdoc}
     */
    public function getWidth()
    {
        return ((int) $this->bag->get('video_width')) ?: null;
    }

    /**
     * {@inheritdoc}
     */
    public function getHeight()
    {
        return ((int) $this->bag->get('video_height')) ?: null;
    }

    /**
     * {@inheritdoc}
     */
    public function getPublishedTime()
    {
        $keys = [
            'article:published_time',
            'created',
            'date',
            'datepublished',
            'datePublished',
            'newsrepublic:publish_date',
            'pagerender',
            'pub_date',
            'publication-date',
            'publish-date',
            'rc.datecreation',
            'timestamp',
            'article:modified_time',
            'eomportal-lastupdate',
            'shareaholic:article_published_time',
        ];

        foreach ($keys as $key) {
            if ($found = $this->bag->get($key)) {
                return $found;
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getLicense()
    {
        return $this->bag->get('copyright');
    }

    /**
     * {@inheritdoc}
     */
    public function getLinkedData()
    {
        $data = [];

        if (!($html = $this->adapter->getResponse()->getHtmlContent())) {
            return $data;
        }

        foreach ($html->getElementsByTagName('script') as $script) {
            if ($script->hasAttribute('type') && strtolower($script->getAttribute('type')) === 'application/ld+json') {
                $value = trim($script->nodeValue);

                if (empty($value)) {
                    continue;
                }

                try {
                    $data[] = json_decode($value);
                } catch (Exception $exception) {
                    continue;
                }
            }
        }

        return $data;
    }

    /**
     * Extract information from the <link> elements.
     *
     * @param DOMDocument $html
     */
    private function extractLinks(DOMDocument $html)
    {
        foreach ($html->getElementsByTagName('link') as $link) {
            if ($link->hasAttribute('rel') && $link->hasAttribute('href')) {
                $rel = trim(strtolower($link->getAttribute('rel')));
                $href = $link->getAttribute('href');

                if (empty($href)) {
                    continue;
                }

                switch ($rel) {
                    case 'favicon':
                    case 'favico':
                    case 'icon':
                    case 'shortcut icon':
                    case 'apple-touch-icon-precomposed':
                    case 'apple-touch-icon':
                        $this->bag->add('icons', $href);
                        break;

                    case 'image_src':
                        $this->bag->add('images', $href);
                        break;

                    case 'alternate':
                        switch ($link->getAttribute('type')) {
                            case 'application/atom+xml':
                            case 'application/json':
                            case 'application/rdf+xml':
                            case 'application/rss+xml':
                            case 'application/xml':
                            case 'text/xml':
                                $this->bag->add('feeds', $href);
                                break;
                        }
                        break;

                    default:
                        $this->bag->set($rel, $href);
                }
            }
        }
    }

    /**
     * Extract information from the <meta> elements.
     *
     * @param DOMDocument $html
     */
    private function extractMetas(DOMDocument $html)
    {
        foreach ($html->getElementsByTagName('meta') as $meta) {
            $value = $meta->getAttribute('content');

            if (empty($value)) {
                continue;
            }

            if ($meta->hasAttribute('name')) {
                $name = trim(strtolower($meta->getAttribute('name')));

                switch ($name) {
                    case 'msapplication-tileimage':
                        $this->bag->add('icons', $value);
                        continue 2;

                    default:
                        $this->bag->set($name, $value);
                        continue 2;
                }
            }

            if ($meta->hasAttribute('itemprop')) {
                $this->bag->set($meta->getAttribute('itemprop'), $value);
            }

            if ($meta->hasAttribute('http-equiv')) {
                $this->bag->set($meta->getAttribute('http-equiv'), $value);
            }

            if ($meta->hasAttribute('property')) {
                $this->bag->set($meta->getAttribute('property'), $value);
            }
        }
    }

    /**
     * Extract <img> elements.
     *
     * @param DOMDocument $html
     */
    private function extractImages(DOMDocument $html)
    {
        if ($this->adapter->getConfig('html[max_images]') === 0) {
            return;
        }

        //Extract only from the main element
        $main = self::getMainElement($html);

        if (!$main) {
            return;
        }

        $url = $this->adapter->getResponse()->getUrl();
        $externalImages = $this->adapter->getConfig('html[external_images]');

        foreach ($main->getElementsByTagName('img') as $img) {
            if (!$img->hasAttribute('src')) {
                continue;
            }

            try {
                $src = $url->createAbsolute($img->getAttribute('src'));
            } catch (Exception $exception) {
                continue;
            }

            //Avoid external images
            if (!self::imageIsValid($src, $url, $externalImages)) {
                continue;
            }

            $parent = $img->parentNode;

            //The image is in a link
            while ($parent && isset($parent->tagName)) {
                if ($parent->tagName === 'a') {
                    //The link is external
                    if ($parent->hasAttribute('href')) {
                        try {
                            $href = $url->createAbsolute($parent->getAttribute('href'));
                        } catch (Exception $exception) {
                            continue 2;
                        }

                        if (!self::imageIsValid($href, $url, $externalImages)) {
                            continue 2;
                        }
                    }

                    //The link has rel=nofollow
                    if ($parent->hasAttribute('rel') && (string) $parent->getAttribute('rel') === 'nofollow') {
                        continue 2;
                    }

                    break;
                }

                $parent = $parent->parentNode;
            }

            $this->bag->add('images', (string) $src);
        }
    }

    /**
     * Check whether a image url is valid or not.
     *
     * @param Url   $url
     * @param Url   $baseUrl
     * @param mixed $externalImages
     *
     * @return bool
     */
    private static function imageIsValid(Url $url, Url $baseUrl, $externalImages)
    {
        //base64 or same domain
        if ($url->getContent() !== null || $url->getDomain() === $baseUrl->getDomain()) {
            return true;
        }

        return is_bool($externalImages) ? $externalImages : $url->match($externalImages);
    }

    /**
     * Returns the main element of the document.
     *
     * @param DOMDocument $html
     *
     * @return DOMElement
     */
    private static function getMainElement(DOMDocument $html)
    {
        // <main>
        $content = $html->getElementsByTagName('main');

        if ($content->length !== 0) {
            return $content->item(0);
        }

        // Popular ids: #main, #content, #page
        $content = $html->getElementById('main') ?: $html->getElementById('content') ?: $html->getElementById('page');

        if ($content) {
            return $content;
        }

        // Wordpress ids: #post-*
        foreach ($html->getElementsByTagName('article') as $article) {
            if ($article->hasAttribute('id') && (strpos($article->getAttribute('id'), 'post-') === 0)) {
                return $article;
            }
        }

        // Returns <body> or <html>
        return $html->getElementsByTagName('body')->item(0) ?: $html->getElementsByTagName('html')->item(0);
    }
}


code:
<?php

namespace Embed\Providers;

use Embed\Adapters\Adapter;
use Embed\Utils;

/**
 * Provider to get the data from the Linkpulse elements in the HTML
 * http://docs.linkpulse.com/docs/opengraph/
 */
class Linkpulse extends Provider
{
    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        if (!($html = $adapter->getResponse()->getHtmlContent())) {
            return;
        }

        foreach ($html->getElementsByTagName('meta') as $meta) {
            $name = trim(strtolower($meta->getAttribute('property')));
            $value = $meta->getAttribute('content');

            if (empty($name)) {
                $name = trim(strtolower($meta->getAttribute('name')));
            }

            if (empty($name) || empty($value)) {
                continue;
            }

            if (strpos($name, 'lp:') === 0) {
                $name = substr($name, 3);
            } elseif (strpos($name, 'lp.article:') !== 0) {
                continue;
            }

            $this->bag->set($name, $value);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
        return $this->bag->get('title');
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription()
    {
        return $this->bag->get('description');
    }

    /**
     * {@inheritdoc}
     */
    public function getUrl()
    {
        $url = $this->normalizeUrl($this->bag->get('url'));

        if ($url !== $this->adapter->getResponse()->getUrl()->getAbsolute('/')) {
            return $url;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTags()
    {
        return (array) $this->bag->get('article.section') ?: [];
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorName()
    {
        return $this->bag->get('article.author');
    }

    /**
     * {@inheritdoc}
     */
    public function getImagesUrls()
    {
        return $this->normalizeUrls((array) $this->bag->get('image'));
    }

    /**
     * {@inheritdoc}
     */
    public function getPublishedTime()
    {
        return $this->bag->get('article.published_time')
            ?: $this->bag->get('article.modified_time');
    }
}


code:
<?php

namespace Embed\Providers;

use Embed\Adapters\Adapter;
use Embed\Http\Response;
use Embed\Http\Url;

/**
 * Provider to get the data using the oEmbed API
 */
class OEmbed extends Provider
{
    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        $endPoint = $this->getEndPoint();

        if ($endPoint) {
            $this->extractOembed($adapter->getDispatcher()->dispatch($endPoint));
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
        return $this->bag->get('title');
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription()
    {
        return $this->bag->get('description');
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        $type = $this->bag->get('type');

        if (strpos($type, ':') !== false) {
            $type = substr(strrchr($type, ':'), 1);
        }

        switch ($type) {
            case 'video':
            case 'photo':
            case 'link':
            case 'rich':
                return $type;

            case 'movie':
                return 'video';
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTags()
    {
        if ($this->bag->has('meta[keywords]')) {
            //it means we are using iframe.ly api
            return array_map('trim', explode(',', $this->bag->get('meta[keywords]')));
        }

        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function getCode()
    {
        return $this->bag->get('html', true);
    }

    /**
     * {@inheritdoc}
     */
    public function getUrl()
    {
        if ($this->getType() === 'photo') {
            return $this->normalizeUrl($this->bag->get('web_page'));
        }

        return $this->normalizeUrl($this->bag->get('url') ?: $this->bag->get('web_page'));
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorName()
    {
        return $this->bag->get('author_name');
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorUrl()
    {
        return $this->normalizeUrl($this->bag->get('author_url'));
    }

    /**
     * {@inheritdoc}
     */
    public function getProviderName()
    {
        return $this->bag->get('provider_name');
    }

    /**
     * {@inheritdoc}
     */
    public function getProviderUrl()
    {
        return $this->normalizeUrl($this->bag->get('provider_url'));
    }

    /**
     * {@inheritdoc}
     */
    public function getImagesUrls()
    {
        $images = [];

        if ($this->getType() === 'photo') {
            $images[] = $this->bag->get('url');
        }

        foreach (['image', 'thumbnail', 'thumbnail_url'] as $type) {
            if ($this->bag->has($type)) {
                $ret = $this->bag->get($type);

                if (is_array($ret)) {
                    $images = array_merge($images, $ret);
                } else {
                    $images[] = $ret;
                }
            }
        }

        return $this->normalizeUrls($images);
    }

    /**
     * {@inheritdoc}
     */
    public function getWidth()
    {
        return $this->bag->get('width');
    }

    /**
     * {@inheritdoc}
     */
    public function getHeight()
    {
        return $this->bag->get('height');
    }

    /**
     * {@inheritdoc}
     */
    public function getLicense()
    {
        return $this->bag->get('license_url');
    }

    /**
     * @return Url|null
     */
    private function getEndPoint()
    {
        //Search using the domain
        $class = 'Embed\\Providers\\OEmbed\\'.$this->adapter->getResponse()->getUrl()->getClassNameForDomain();
        $extraParameters = (array) $this->adapter->getConfig('oembed[parameters]');

        if (class_exists($class)) {
            $endPoint = $class::create($this->adapter);

            if ($endPoint && ($url = $endPoint->getEndPoint())) {
                return $url->withAddedQueryParameters($extraParameters);
            }
        }

        //Search in the DOM
        $endPoint = OEmbed\DOM::create($this->adapter);

        if ($endPoint && ($url = $endPoint->getEndPoint())) {
            return $url->withAddedQueryParameters($extraParameters);
        }

        //Try with embedly
        $endPoint = OEmbed\Embedly::create($this->adapter);

        if ($endPoint && ($url = $endPoint->getEndPoint())) {
            return $url->withAddedQueryParameters($extraParameters);
        }

        //Try with iframely
        $endPoint = OEmbed\Iframely::create($this->adapter);

        if ($endPoint && ($url = $endPoint->getEndPoint())) {
            return $url->withAddedQueryParameters($extraParameters);
        }
    }

    /**
     * Save the oembed data in the bag.
     *
     * @param Response $response
     */
    private function extractOembed(Response $response)
    {
        // extract from xml
        if (($response->getUrl()->getExtension() === 'xml') || ($response->getUrl()->getQueryParameter('format') === 'xml')) {
            if ($xml = $response->getXmlContent()) {
                foreach ($xml as $element) {
                    $content = trim((string) $element);

                    if (stripos($content, '<![CDATA[') === 0) {
                        $content = substr($content, 9, -3);
                    }

                    $this->bag->set($element->getName(), $content);
                }
            }
            // extract from json
        } else {
            if (($json = $response->getJsonContent()) && empty($json['Error'])) {
                $this->bag->set($json);
            }
        }
    }
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Amcharts extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'live.amcharts.com/*';
    protected static $endPoint = 'https://live.amcharts.com/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Bambuser extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'bambuser.com/v/*';
    protected static $endPoint = 'https://api.bambuser.com/oembed.json';
}


code:
<?php

namespace Embed\Providers\OEmbed;

use Embed\Adapters\Adapter;
use Embed\Http\Response;
use Embed\Http\Url;
use DOMDocument;
use Exception;

/**
 * Class to detect the oembed endPoint.
 */
class DOM implements EndPointInterface
{
    protected $response;

    /**
     * Create a instance of a OEmbedEndPoint.
     *
     * @param Adapter $adapter
     *
     * @return static
     */
    public static function create(Adapter $adapter)
    {
        return new static($adapter->getResponse());
    }

    /**
     * Construct.
     *
     * @param Response $response
     */
    protected function __construct(Response $response)
    {
        $this->response = $response;
    }

    /**
     * Returns the oembed endPoint.
     *
     * @return Url|null
     */
    public function getEndPoint()
    {
        $html = $this->response->getHtmlContent();

        if ($html && ($url = self::getEndPointFromDom($html))) {
            try {
                $endPoint = $this->response->getUrl()->createAbsolute($url);
            } catch (Exception $exception) {
                return null;
            }

            if ($endPoint->getExtension() !== 'xml' && !$endPoint->hasQueryParameter('format')) {
                return $endPoint->withQueryParameter('format', 'json');
            }

            return $endPoint;
        }
    }

    /**
     * Extract oembed information from the <link rel="alternate"> elements
     * Note: Some sites use <meta rel="alternate"> instead.
     *
     * @param DOMDocument $html
     *
     * @return string|null
     */
    protected static function getEndPointFromDom(DOMDocument $html)
    {
        foreach (['link', 'meta'] as $tagName) {
            foreach ($html->getElementsByTagName($tagName) as $link) {
                $rel = trim(strtolower($link->getAttribute('rel')));
                $href = $link->getAttribute('href');

                if (empty($href)) {
                    continue;
                }

                if ($rel === 'alternate') {
                    switch (strtolower($link->getAttribute('type'))) {
                        case 'application/json+oembed':
                        case 'application/xml+oembed':
                        case 'text/json+oembed':
                        case 'text/xml+oembed':
                            return $href;
                    }
                }
            }
        }
    }
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Dailymotion extends EndPoint implements EndPointInterface
{
    protected static $pattern = '*dailymotion.*';
    protected static $endPoint = 'http://www.dailymotion.com/services/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

use Embed\Http\Url;

class Deviantart extends EndPoint implements EndPointInterface
{
    protected static $pattern = [
        '*.deviantart.com/art/*',
        'www.deviantart.com/#/d*',
    ];
    protected static $endPoint = 'http://backend.deviantart.com/oembed';

    /**
     * {@inheritdoc}
     */
    public function getEndPoint()
    {
        return Url::create(static::$endPoint)
                ->withQueryParameters([
                    'url' => (string) $this->response->getUrl(),
                    'format' => 'json',
                    'for' => 'embed',
                ]);
    }
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Dotsub extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'dotsub.com/view/*';
    protected static $endPoint = 'http://dotsub.com/services/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

use Embed\Adapters\Adapter;
use Embed\Http\Response;
use Embed\Http\Url;

class Embedly implements EndPointInterface
{
    private $response;
    private $key;

    /**
     * {@inheritdoc}
     */
    public static function create(Adapter $adapter)
    {
        $key = $adapter->getConfig('oembed[embedly_key]');

        if (!empty($key)) {
            return new static($adapter->getResponse(), $key);
        }
    }

    /**
     * Constructor.
     *
     * @param Response $response
     * @param string   $key
     */
    protected function __construct(Response $response, $key)
    {
        $this->response = $response;
        $this->key = $key;
    }

    /**
     * {@inheritdoc}
     */
    public function getEndPoint()
    {
        return Url::create('http://api.embed.ly/1/oembed')
                ->withQueryParameters([
                    'url' => (string) $this->response->getUrl(),
                    'format' => 'json',
                    'key' => $this->key,
                ]);
    }
}


code:
<?php

namespace Embed\Providers\OEmbed;

use Embed\Adapters\Adapter;
use Embed\Http\Response;
use Embed\Http\Url;

/**
 * Abstract class extended by other classes.
 */
abstract class EndPoint
{
    protected $response;
    protected static $pattern;
    protected static $endPoint;

    /**
     * {@inheritdoc}
     */
    public static function create(Adapter $adapter)
    {
        $response = $adapter->getResponse();

        if ($response->getUrl()->match(static::$pattern)) {
            return new static($response);
        }
    }

    /**
     * Constructor.
     *
     * @param Response $response
     */
    protected function __construct(Response $response)
    {
        $this->response = $response;
    }

    /**
     * {@inheritdoc}
     */
    public function getEndPoint()
    {
        return Url::create(static::$endPoint)
                ->withQueryParameters([
                    'url' => (string) $this->response->getUrl(),
                    'format' => 'json',
                ]);
    }
}


code:
<?php

namespace Embed\Providers\OEmbed;

use Embed\Adapters\Adapter;
use Embed\Http\Url;

/**
 * Interface for all oembed endPoint.
 */
interface EndPointInterface
{
    /**
     * Check the response and create new instance.
     *
     * @param Adapter $adapter
     *
     * @return EndPointInterface|null
     */
    public static function create(Adapter $adapter);

    /**
     * Returns the oembed endPoint.
     *
     * @return Url|null
     */
    public function getEndPoint();
}


code:
<?php

namespace Embed\Providers\OEmbed;

use Embed\Http\Url;

class Facebook extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'www.facebook.com/*';

    /**
     * {@inheritdoc}
     */
    public function getEndPoint()
    {
        if ($this->response->getUrl()->match(['*/videos/*', '/video.php'])) {
            $endPoint = Url::create('https://www.facebook.com/plugins/video/oembed.json');
        } else {
            $endPoint = Url::create('https://www.facebook.com/plugins/post/oembed.json');
        }

        return $endPoint->withQueryParameters([
            'url' => (string) $this->response->getUrl(),
            'format' => 'json',
        ]);
    }
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Flickr extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'www.flickr.com/*';
    protected static $endPoint = 'http://flickr.com/services/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

use Embed\Adapters\Adapter;
use Embed\Http\Response;
use Embed\Http\Url;

class Iframely implements EndPointInterface
{
    private $response;
    private $key;

    /**
     * {@inheritdoc}
     */
    public static function create(Adapter $adapter)
    {
        $key = $adapter->getConfig('oembed[iframely_key]');

        if (!empty($key)) {
            return new static($adapter->getResponse(), $key);
        }
    }

    /**
     * Constructor.
     *
     * @param Response $response
     * @param string   $key
     */
    protected function __construct(Response $response, $key)
    {
        $this->response = $response;
        $this->key = $key;
    }

    /**
     * {@inheritdoc}
     */
    public function getEndPoint()
    {
        return Url::create('http://open.iframe.ly/api/oembed')
                ->withQueryParameters([
                    'url' => (string) $this->response->getUrl(),
                    'format' => 'json',
                    'api_key' => $this->key,
                ]);
    }
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Imgur extends EndPoint implements EndPointInterface
{
    protected static $pattern = [
        'imgur.com/*',
        'i.imgur.com/*',
    ];
    protected static $endPoint = 'https://api.imgur.com/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

use Embed\Http\Url;

class Infogram extends EndPoint implements EndPointInterface
{
    protected static $pattern = [
        'infogr.am/*',
        'www.infogr.am/*',
    ];
    protected static $endPoint = 'https://infogr.am/oembed';

    /**
     * {@inheritdoc}
     */
    public function getEndPoint()
    {
        $url = $this->response->getUrl()->withScheme('https');

        return Url::create(static::$endPoint)
                ->withQueryParameters([
                    'url' => (string) $url,
                ]);
    }
}


code:
<?php

namespace Embed\Providers\OEmbed;

use Embed\Http\Url;

class Instagram extends EndPoint implements EndPointInterface
{
    protected static $pattern = [
        'instagram.com/p/*',
        'www.instagram.com/p/*',
    ];
    protected static $endPoint = 'https://api.instagram.com/oembed';

    /**
     * {@inheritdoc}
     */
    public function getEndPoint()
    {
        $url = $this->response->getUrl()->withScheme('http');

        return Url::create(static::$endPoint)
                ->withQueryParameters([
                    'url' => (string) $url,
                    'format' => 'json',
                ]);
    }
}


code:
<?php

namespace Embed\Providers\OEmbed;

use Embed\Http\Url;

class Jsbin extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'output.jsbin.com/*';
    protected static $endPoint = 'http://jsbin.com/oembed';

    /**
     * {@inheritdoc}
     */
    public function getEndPoint()
    {
        $url = $this->response->getUrl()->withDirectoryPosition(2, 'embed');

        return Url::create(static::$endPoint)
                ->withQueryParameters([
                    'url' => (string) $url,
                    'format' => 'json',
                ]);
    }
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Kickstarter extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'www.kickstarter.com/*';
    protected static $endPoint = 'http://www.kickstarter.com/services/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Meetup extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'www.meetup.com/*';
    protected static $endPoint = 'http://api.meetup.com/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Photobucket extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'photobucket.com/*';
    protected static $endPoint = 'http://s51.photobucket.com/oembed/';
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Poll extends Polldaddy
{
    protected static $pattern = 'poll.fm/*';
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Polldaddy extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'polldaddy.com/poll/*';
    protected static $endPoint = 'https://api.crowdsignal.com/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Reddit extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'www.reddit.com/*';
    protected static $endPoint = 'https://www.reddit.com/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

use Embed\Http\Url;

class Scribd extends EndPoint implements EndPointInterface
{
    protected static $pattern = [
        'www.scribd.com/doc/*',
        'www.scribd.com/document/*',
    ];
    protected static $endPoint = 'http://www.scribd.com/services/oembed';

    /**
     * {@inheritdoc}
     */
    public function getEndPoint()
    {
        $url = $this->response->getUrl()->withDirectoryPosition(0, 'doc');

        return Url::create(static::$endPoint)
                ->withQueryParameters([
                    'url' => (string) $url,
                    'format' => 'json',
                ]);
    }
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Shoudio extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'shoudio.com/*';
    protected static $endPoint = 'http://shoudio.com/api/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Smugmug extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'www.smugmug.com/*';
    protected static $endPoint = 'http://api.smugmug.com/services/oembed/';
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Soundcloud extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'soundcloud.com/*';
    protected static $endPoint = 'https://soundcloud.com/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

use Embed\Http\Url;

class Spotify extends EndPoint implements EndPointInterface
{
    protected static $pattern = '*.spotify.com/*';
    protected static $endPoint = 'https://embed.spotify.com/oembed';

    /**
     * {@inheritdoc}
     */
    public function getEndPoint()
    {
        return Url::create(static::$endPoint)
                ->withQueryParameters([
                    'url' => (string) $this->response->getUrl()->withQueryParameters([]),
                    'format' => 'json'
                ]);
    }
}


code:
<?php

namespace Embed\Providers\OEmbed;

use Embed\Adapters\Adapter;
use Embed\Http\Response;
use Embed\Http\Url;

class Tumblr extends EndPoint implements EndPointInterface
{
    protected static $pattern = [
        '*.tumblr.com/post/*'
    ];

    protected static $endPoint = 'https://www.tumblr.com/oembed/1.0';

    /**
     * {@inheritdoc}
     */
    public static function create(Adapter $adapter)
    {
        $response = $adapter->getResponse();

        if ($response->getStartingUrl()->match(static::$pattern)) {
            return new static($response);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getEndPoint()
    {
        return Url::create(static::$endPoint)
                ->withQueryParameters([
                    'url' => (string) $this->response->getStartingUrl()
                ]);
    }
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Twitch extends EndPoint implements EndPointInterface
{
    protected static $pattern = '*.twitch.tv/*';
    protected static $endPoint = 'https://api.twitch.tv/v5/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Twitter extends EndPoint implements EndPointInterface
{
    protected static $pattern = ['*.twitter.com/*', 'twitter.com/*'];
    protected static $endPoint = 'https://publish.twitter.com/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Ustream extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'www.ustream.tv/*';
    protected static $endPoint = 'http://www.ustream.tv/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

class WordPress extends EndPoint implements EndPointInterface
{
    protected static $pattern = 'wordpress.tv/*';
    protected static $endPoint = 'https://wordpress.tv/oembed';
}


code:
<?php

namespace Embed\Providers\OEmbed;

class Youtube extends EndPoint implements EndPointInterface
{
    protected static $pattern = ['*youtube.*', '*youtu\.be.*'];
    protected static $endPoint = 'http://www.youtube.com/oembed';
}


code:
<?php

namespace Embed\Providers;

use Embed\Adapters\Adapter;
use Embed\Utils;

/**
 * Provider to get the data from the Open Graph elements in the HTML
 */
class OpenGraph extends Provider
{
    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        if (!($html = $adapter->getResponse()->getHtmlContent())) {
            return;
        }

        foreach ($html->getElementsByTagName('meta') as $meta) {
            $name = trim(strtolower($meta->getAttribute('property')));
            $value = $meta->getAttribute('content');

            if (empty($name)) {
                $name = trim(strtolower($meta->getAttribute('name')));
            }

            if (empty($name) || empty($value)) {
                continue;
            }

            if (strpos($name, 'og:') === 0) {
                $name = substr($name, 3);
            } elseif (
                    strpos($name, 'article:') !== 0
                 && strpos($name, 'image:') !== 0
                 && strpos($name, 'video:') !== 0
                 && strpos($name, 'book:') !== 0
                 && strpos($name, 'music:') !== 0
                 && strpos($name, 'profile:') !== 0
            ) {
                continue;
            }

            switch ($name) {
                case 'image':
                case 'image:url':
                case 'image:secure_url':
                    $this->bag->add('images', $value);
                    break;

                case 'article:tag':
                case 'video:tag':
                case 'book:tag':
                    $this->bag->add('tags', $value);
                    break;

                default:
                    $this->bag->set($name, $value);
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
        return $this->bag->get('title');
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription()
    {
        return $this->bag->get('description');
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        $type = $this->bag->get('type');

        if (strpos($type, ':') !== false) {
            $type = substr(strrchr($type, ':'), 1);
        }

        switch ($type) {
            case 'video':
            case 'photo':
            case 'link':
            case 'rich':
                return $type;

            case 'article':
                return 'link';
        }

        if ($this->bag->has('video')) {
            return 'video';
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getCode()
    {
        $names = ['video:secure_url', 'video:url', 'video'];

        foreach ($names as $name) {
            if ($this->bag->has($name)) {
                $video = $this->normalizeUrl($this->bag->get($name));

                if (!($videoPath = parse_url($video, PHP_URL_PATH)) || !($type = pathinfo($videoPath, PATHINFO_EXTENSION))) {
                    $type = $this->bag->get('video:type');
                }

                switch ($type) {
                    case 'swf':
                    case 'application/x-shockwave-flash':
                        return Utils::flash($video, $this->getWidth(), $this->getHeight());

                    case 'mp4':
                    case 'ogg':
                    case 'ogv':
                    case 'webm':
                    case 'application/mp4':
                    case 'video/mp4':
                    case 'video/ogg':
                    case 'video/ogv':
                    case 'video/webm':
                        $images = $this->getImagesUrls();

                        return Utils::videoHtml(current($images), $video, $this->getWidth(), $this->getHeight());

                    case 'text/html':
                        return Utils::iframe($video, $this->getWidth(), $this->getHeight());
                }
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getUrl()
    {
        return $this->normalizeUrl($this->bag->get('url'));
    }

    /**
     * {@inheritdoc}
     */
    public function getTags()
    {
        return (array) $this->bag->get('tags') ?: [];
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorName()
    {
        return $this->bag->get('article:author') ?: $this->bag->get('book:author');
    }

    /**
     * {@inheritdoc}
     */
    public function getProviderName()
    {
        return $this->bag->get('site_name');
    }

    /**
     * {@inheritdoc}
     */
    public function getImagesUrls()
    {
        return $this->normalizeUrls($this->bag->get('images'));
    }

    /**
     * {@inheritdoc}
     */
    public function getWidth()
    {
        return $this->bag->get('image:width') ?: $this->bag->get('video:width');
    }

    /**
     * {@inheritdoc}
     */
    public function getHeight()
    {
        return $this->bag->get('image:height') ?: $this->bag->get('video:height');
    }

    /**
     * {@inheritdoc}
     */
    public function getPublishedTime()
    {
        return $this->bag->get('article:published_time')
            ?: $this->bag->get('article:modified_time')
            ?: $this->bag->get('video:release_date')
            ?: $this->bag->get('music:release_date');
    }

    /**
     * {@inheritdoc}
     */
    public function getProviderUrl()
    {
        return $this->bag->get('website');
    }
}


code:
<?php

namespace Embed\Providers;

use Embed\Adapters\Adapter;
use Embed\DataInterface;
use Embed\Bag;

/**
 * Abstract class used by all providers.
 */
abstract class Provider implements DataInterface
{
    protected $bag;
    protected $adapter;

    /**
     * Constructor.
     *
     * @param Adapter $adapter
     */
    public function __construct(Adapter $adapter)
    {
        $this->bag = new Bag();
        $this->adapter = $adapter;
    }

    /**
     * Returns the bag containing all data.
     *
     * @return Bag
     */
    public function getBag()
    {
        return $this->bag;
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getTags()
    {
        return [];
    }
    /**
     * {@inheritdoc}
     */
    public function getFeeds()
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function getCode()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getUrl()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorName()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorUrl()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getProviderIconsUrls()
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function getProviderName()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getProviderUrl()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getImagesUrls()
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function getWidth()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getHeight()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getPublishedTime()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getLicense()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getLinkedData()
    {
        return [];
    }

    /**
     * Returns the urls as absolute.
     *
     * @param mixed $urls
     *
     * @return array
     */
    protected function normalizeUrls($urls)
    {
        if (!is_array($urls)) {
            return [];
        }

        return array_map([$this, 'normalizeUrl'], array_filter($urls));
    }

    /**
     * Returns the url as absolute.
     *
     * @param string|null $url
     *
     * @return string|null
     */
    protected function normalizeUrl($url)
    {
        if (empty($url)) {
            return;
        }

        return $this->adapter->getResponse()->getUrl()->getAbsolute($url);
    }
}


code:
<?php

namespace Embed\Providers;

use Embed\Adapters\Adapter;

/**
 * Provider to get the data from the Sailthru data elements in the HTML
 */
class Sailthru extends Provider
{
    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        if (!($html = $adapter->getResponse()->getHtmlContent())) {
            return;
        }

        foreach ($html->getElementsByTagName('meta') as $meta) {
            $name = trim(strtolower($meta->getAttribute('name')));
            $value = $meta->getAttribute('content');

            if (empty($name) || empty($value)) {
                continue;
            }

            if (strpos($name, 'sailthru.') === 0) {
                $this->bag->set(substr($name, 9), $value);
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getImagesUrls()
    {
        $images = [];

        foreach ($this->bag->getKeys() as $name) {
            if (strpos($name, 'image') !== false) {
                $images[] = $this->bag->get($name);
            }
        }

        return $this->normalizeUrls($images);
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorName()
    {
        return $this->bag->get('author');
    }

    /**
     * {@inheritdoc}
     */
    public function getPublishedTime()
    {
        return $this->bag->get('date');
    }
}


code:
<?php

namespace Embed\Providers;

use Embed\Adapters\Adapter;
use Embed\Utils;

/**
 * Provider to get the data from the Twitter Cards elements in the HTML
 */
class TwitterCards extends Provider
{
    /**
     * {@inheritdoc}
     */
    public function __construct(Adapter $adapter)
    {
        parent::__construct($adapter);

        if (!($html = $adapter->getResponse()->getHtmlContent())) {
            return;
        }

        foreach ($html->getElementsByTagName('meta') as $meta) {
            $name = trim(strtolower($meta->getAttribute('name')));
            $value = $meta->getAttribute('content') ?: $meta->getAttribute('value');

            if (empty($name) || empty($value)) {
                continue;
            }

            if (strpos($name, 'twitter:') === 0) {
                $name = substr($name, 8);

                if ($name === 'image' || $name === 'image:src') {
                    $this->bag->add('images', $value);
                } else {
                    $this->bag->set($name, $value);
                }
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
        return $this->bag->get('title');
    }

    /**
     * {@inheritdoc}
     */
    public function getDescription()
    {
        return $this->bag->get('description');
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        $type = $this->bag->get('card');

        if (strpos($type, ':') !== false) {
            $type = substr(strrchr($type, ':'), 1);
        }

        switch ($type) {
            case 'summary':
            case 'summary_large_image':
            case 'app':
                return 'rich';
            case 'player':
                return 'video';
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getCode()
    {
        $src = $this->normalizeUrl($this->bag->get('player'));

        if ($src !== null) {
            return Utils::iframe($src, $this->getWidth(), $this->getHeight());
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getUrl()
    {
        return $this->normalizeUrl($this->bag->get('url'));
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorName()
    {
        return $this->bag->get('creator');
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthorUrl()
    {
        $author = $this->getAuthorName();

        if (!empty($author)) {
            return 'https://twitter.com/'.ltrim($author, '@');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getImagesUrls()
    {
        return $this->normalizeUrls($this->bag->get('images'));
    }

    /**
     * {@inheritdoc}
     */
    public function getWidth()
    {
        return $this->bag->get('player:width');
    }

    /**
     * {@inheritdoc}
     */
    public function getHeight()
    {
        return $this->bag->get('player:height');
    }
}


code:
<?php

namespace Embed;

use DOMDocument;
use DOMElement;
use DOMNodeList;
use DOMXPath;

/**
 * Some helpers methods used across the library.
 */
class Utils
{
    private static $encodings = [
        'ASCII'        => 'ascii',
        'UTF-8'        => 'utf-8',
        'SJIS'         => 'shift_jis',
        'Windows-1251' => 'windows-1251',
        'Windows-1252' => 'windows-1252',
        'Windows-1254' => 'windows-1254',
        'ISO-8859-1'   => 'iso-8859-1',
        'ISO-8859-2'   => 'iso-8859-2',
        'ISO-8859-3'   => 'iso-8859-3',
        'ISO-8859-4'   => 'iso-8859-4',
        'ISO-8859-5'   => 'iso-8859-5',
        'ISO-8859-6'   => 'iso-8859-6',
        'ISO-8859-7'   => 'iso-8859-7',
        'ISO-8859-8'   => 'iso-8859-8',
        'ISO-8859-9'   => 'iso-8859-9',
        'ISO-8859-10'  => 'iso-8859-10',
        'ISO-8859-13'  => 'iso-8859-13',
        'ISO-8859-14'  => 'iso-8859-14',
        'ISO-8859-15'  => 'iso-8859-15',
        'ISO-8859-16'  => 'iso-8859-16',
    ];

    /**
     * Creates a <video> element.
     *
     * @param string       $poster  Poster attribute
     * @param string|array $sources Video sources
     * @param int          $width   Width attribute
     * @param int          $height  Height attribute
     *
     * @return string
     */
    public static function videoHtml($poster, $sources, $width = 0, $height = 0)
    {
        $code = self::element('video', [
            'poster' => ($poster ?: null),
            'width' => ($width ?: null),
            'height' => ($height ?: null),
            'controls' => true,
        ]);

        foreach ((array) $sources as $source) {
            $code .= self::element('source', ['src' => $source]);
        }

        return $code.'</video>';
    }

    /**
     * Creates an <audio> element.
     *
     * @param string|array $sources Audio sources
     *
     * @return string
     */
    public static function audioHtml($sources)
    {
        $code = '<audio controls>';

        foreach ((array) $sources as $source) {
            $code .= self::element('source', ['src' => $source]);
        }

        return $code.'</audio>';
    }

    /**
     * Creates an <img> element.
     *
     * @param string $src    Image source attribute
     * @param string $alt    Alt attribute
     * @param int    $width  Width attribute
     * @param int    $height Height attribute
     *
     * @return string
     */
    public static function imageHtml($src, $alt = '', $width = 0, $height = 0)
    {
        return self::element('img', [
            'src' => $src,
            'alt' => $alt,
            'width' => ($width ?: null),
            'height' => ($height ?: null),
        ]);
    }

    /**
     * Creates an <iframe> element.
     *
     * @param string $src    Iframe source attribute
     * @param int    $width  Width attribute
     * @param int    $height Height attribute
     * @param int    $styles Extra css styles
     *
     * @return string
     */
    public static function iframe($src, $width = 0, $height = 0, $styles = '')
    {
        $width = $width ? (is_int($width) ? $width.'px' : $width) : '600px';
        $height = $height ? (is_int($height) ? $height.'px' : $height) : '400px';

        if (empty($styles)) {
            $styles = 'border:none;overflow:hidden;';
        }

        $styles .= "width:{$width};height:{$height};";

        return self::element('iframe', [
            'src' => $src,
            'frameborder' => 0,
            'allowTransparency' => 'true',
            'style' => $styles,
        ]).'</iframe>';
    }

    /**
     * Creates an <script> element.
     *
     * @param string $src The src attribute
     *
     * @return string
     */
    public static function script($src)
    {
        return self::element('script', [
            'src' => $src,
        ]).'</script>';
    }

    /**
     * Creates an <iframe> element with a google viewer.
     *
     * @param string $src The file loaded by the viewer (pdf, doc, etc)
     *
     * @return string
     */
    public static function google($src)
    {
        return self::iframe('https://docs.google.com/viewer?'.http_build_query([
            'url' => $src,
            'embedded' => 'true',
        ]), 600, 600);
    }

    /**
     * Creates a flash element.
     *
     * @param string   $src    The swf file source
     * @param null|int $width  Width attribute
     * @param null|int $height Height attribute
     *
     * @return string
     */
    public static function flash($src, $width = null, $height = null)
    {
        $code = self::element('object', [
            'width' => $width ?: 600,
            'height' => $height ?: 400,
            'classid' => 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000',
            'codebase' => 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,47,0',
        ]);

        $code .= self::element('param', ['name' => 'movie', 'value' => $src]);
        $code .= self::element('param', ['name' => 'allowFullScreen', 'value' => 'true']);
        $code .= self::element('param', ['name' => 'allowScriptAccess', 'value' => 'always']);
        $code .= self::element('embed', [
            'src' => $src,
            'width' => $width ?: 600,
            'height' => $height ?: 400,
            'type' => 'application/x-shockwave-flash',
            'allowFullScreen' => 'true',
            'allowScriptAccess' => 'always',
            'pluginspage' => 'http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash',
        ]);

        return $code.'</embed></object>';
    }

    /**
     * Creates an html element.
     *
     * @param string $name  Element name
     * @param array  $attrs Element attributes
     *
     * @return string
     */
    private static function element($name, array $attrs)
    {
        $str = "<$name";

        foreach ($attrs as $name => $value) {
            if ($value === null) {
                continue;
            } elseif ($value === true) {
                $str .= " $name";
            } elseif ($value !== false) {
                $str .= ' '.$name.'="'.htmlspecialchars($value).'"';
            }
        }

        return "$str>";
    }

    /**
     * Convert a string to utf-8.
     *
     * @param string $content The content to convert
     * @param string $charset The original charset
     *
     * @return string
     */
    public static function toUtf8($content, $charset)
    {
        $charset = strtoupper($charset);

        if (empty($charset) || $charset === 'UTF-8') {
            return $content;
        }

        $encodings = array_map('strtoupper', mb_list_encodings());

        if (in_array($charset, $encodings, true)) {
            return mb_convert_encoding($content, 'UTF-8', $charset);
        }

        if (function_exists('iconv')) {
            return iconv($charset, 'UTF-8//TRANSLIT//IGNORE', $content);
        }

        return $content;
    }

    /**
     * Search and return the first element matching with a xpath
     *
     * @param DOMDocument $document
     * @param string      $query
     * @param bool        $returnFirst
     *
     * @return DOMElement|DOMNodeList|null
     */
    public static function xpathQuery(DOMDocument $document, $query, $returnFirst = true)
    {
        $xpath = new DOMXPath($document);
        $entries = $xpath->query($query);

        if ($entries->length) {
            return $returnFirst ? $entries->item(0) : $entries;
        }
    }

    /**
     * Parse a string as html code
     *
     * @param string $html
     *
     * @return DOMDocument
     */
    public static function parse($html)
    {
        $errors = libxml_use_internal_errors(true);
        $entities = libxml_disable_entity_loader(true);

        $html = trim(self::normalize($html));

        $document = new DOMDocument();
        $document->loadHTML($html);

        libxml_use_internal_errors($errors);
        libxml_disable_entity_loader($entities);

        return $document;
    }

    /**
     * Normalize the encoding of a html code before parse
     *
     * @param string $string
     *
     * @return string
     */
    private static function normalize($string)
    {
        $detected = mb_detect_encoding($string, implode(',', array_keys(self::$encodings)), true);

        if ($detected && isset(self::$encodings[$detected])) {
            $string = mb_convert_encoding($string, 'HTML-ENTITIES', $detected);
            $string = preg_replace(
                '/<head[^>]*>/',
                '<head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset='.self::$encodings[$detected].'">',
                $string
            );
        }

        return $string;
    }
}


code:
<?php

spl_autoload_register(function ($class) {
    if (strpos($class, 'Embed\\') !== 0) {
        return;
    }

    $file = __DIR__.str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen('Embed'))).'.php';

    if (is_file($file)) {
        require_once $file;
    }
});