| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 | <?php/** * MediaEmbed * * This file is part of Grav MediaEmbed plugin. * * Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE. * http://benjamin-regler.de/license/ */namespace Grav\Plugin\MediaEmbed;use Grav\Common\Grav;use Grav\Common\GravTrait;use Grav\Plugin\MediaEmbed\Service;use RocketTheme\Toolbox\Event\Event;/** * MediaEmbed * * Helper class to embed several media sites (e.g. YouTube, Vimeo, * Soundcloud) by only providing the URL to the medium. */class MediaEmbed{  /**   * @var MediaEmbed   */	use GravTrait;  /** ---------------------------   * Private/protected properties   * ----------------------------   */  /**   * A unique identifier   *   * @var string   */  protected $id;  /**   * A key-valued array used for hashing math formulas of a page   *   * @var array   */  protected $hashes;  /**   * @var array   */  protected $config;  /**   * @var array   */  protected $assets = [];  /**   * @var Grav\Plugin\MediaEmbed\Service   */  protected $service;  /** -------------   * Public methods   * --------------   */  /**   * Constructor   *   * @param [type] $config [description]   */  public function __construct($config)  {    // Initialize Service class    $this->service = new Service();    $this->config = $config;    $this->hashes = [];    $services = $this->config->get('plugins.mediaembed.services', []);    foreach ($services as $name => $config) {      if (!$config['enabled']) {        continue;      }      // Load providers in directory "services"      $class =  __NAMESPACE__ . "\\Services\\$name";      if (!class_exists($class)) {        // Fallback to a more generic one        $type = isset($config['type']) ? $config['type'] : '';        $class = __NAMESPACE__."\\OEmbed\\OEmbed".ucfirst($type);      }      // Populate config      $config['media'] = $this->config->get('plugins.mediaembed.media', []);      $config['name'] = $name;      if (class_exists($class)) {        // Load ServiceProvider        $provider = new $class($config);        // Register ServiceProvider        $this->service->register($provider);      }    }  }  /**   * Gets and sets the identifier for hashing.   *   * @param  string $var the identifier   *   * @return string      the identifier   */  public function id($var = null)  {    if ($var !== null) {      $this->id = $var;    }    return $this->id;  }  public function prepare($content, $id = '')  {    // Set unique identifier based on page content    $this->id(md5(time() . $id . md5($content)));    // Reset class hashes before processing    $this->reset();    $regex = "~    (               # wrap whole match in $1      !\\[        (?P<alt>.*?)       # alt text = $2      \\]      \\(           # literal paren        [ \\t]*        <?(?P<src>\S+?)>?  # src url = $3        [ \\t]*        (           # $4          (['\"])   # quote char = $5          (?<title>.*?)     # title = $6          \\5       # matching quote          [ \\t]*        )?          # title is optional      \\)    )    ~xs";    // Replace all mediaembed links by a (unique) hash    $content = preg_replace_callback($regex, function($matches) {      // Get the url and parse it      $url = parse_url(htmlspecialchars_decode($matches[3]));      // If there is no host set but there is a path, the file is local      if (!isset($url['host']) && isset($url['path'])) {        return $matches[0];      }      if (!isset($matches['title'])) {        $matches['title'] = '';      }      return $this->hash($matches[0], $matches);    }, $content);    return $content;  }	public function process($content, $config = [])  {    /** @var Twig $twig */    $twig = self::getGrav()['twig'];    // Initialize unique per-page counter    $uid = 1;    // '~(<p>)?\s*<a[^>]*href\s*=\s*([\'"])(?P<href>.*?)\2[^>]*>(?P<code>.*?)</a>\s*(?(1)(</p>))~i',    // Get all <a> tags and extract "href" attribute    $content = preg_replace_callback(      '~mediaembed::([0-9a-z]+)::([0-9]+)::M~i',      function($match) use ($twig, &$uid, $config) {        list($embed, $data) = $this->hashes[$match[0]];        // Check if a service for a specific domain is registered        if ($this->service->match($data['src'])) {          $mediaembed = [            'uid' => $uid++,            'service' => null,            'config' => $config,            'raw' => [              'alt' => $data['alt'],              'title' => $data['title'],              'src' => html_entity_decode($data['src']),            ],            'success' => true,            'message' => '',          ];          // Load and get data of OEmbed Media Service          try {            $provider = $this->service->embed($data['src']);          } catch (\Exception $e) {            $mediaembed['message'] = $e->getMessage();            $mediaembed['success'] = false;          }          // Setup variables for embedding OEmbed Media Service          if ($mediaembed['success']) {            // Get assets/options of current provider            $assets = $provider->onTwigTemplateVariables(              new Event(['service' => $this->service, 'mediaembed' => $this])            );            // Assets are passed by value as an array            if (is_array($assets)) {              $this->addAssets($assets);            }            // Add OEmbed Service to variables            $mediaembed['service'] = $provider;            // TODO: Cache contents from thumbnail and url          }          // Embed OEmbed Media          $vars = ['mediaembed' => $mediaembed];          $template = 'partials/mediaembed' . TEMPLATE_EXT;          $embed = $twig->processTemplate($template, $vars);        } else {          $text = (strlen($data['alt']) > 0) ? $data['alt'] : $data['src'];          // If display link or img          $link = $config->get('link');          if($link == true) {            $attributes = [              'href' => $data['src'],              'title' => $data['title'],            ];            $format = '<a%s>%s</a>';          } else {            $attributes = [              'src' => $data['src'],              'title' => $data['title'],              'alt' => $data['alt'],            ];            $format = '<img%s>';          }          foreach ($attributes as $key => $value) {            if (strlen($value) == 0) {              unset($attributes[$key]);            } else {              $value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');              $attributes[$key] = $key . '="' . $value . '"';            }          }          $attributes = $attributes ? ' ' . implode(' ', $attributes) : '';          // Transform embed media to link or img for compatibility          $embed = sprintf($format, $attributes, $text);        }        return $embed;    }, $content);    $this->reset();    // Write content back to page    return $content;  }  /**   * Fires an event with optional parameters.   *   * @param  string $eventName The name of the event.   * @param  Event  $event     Optional parameter to be passed to the   *                           called methods.   * @return Event   */  public function fireEvent($eventName, Event $event = null)  {    // Dispatch event; just propagate it to service class    return $this->service->call($eventName, $event);  }  /**   * Get assets of loaded media services.   *   * @param boolean $reset Toggle whether to reset assets after retrieving   *                       or not.   */  public function getAssets($reset = true)  {    $assets = $this->assets;    if ($reset) {      $this->assets = [];    }    return $assets;  }  /**   * Add assets to the queue of MediaEmbed plugin   *   * @param array   $assets An array of assets to add.   * @param boolean $append Append assets to array or reset assets.   */  public function addAssets($assets, $append = true)  {    // Append or reset assets    if (!$append) {      $this->assets = [];    }    // Wrap non-array assets in an array    if (!is_array($assets)) {      $assets = array($assets);    }    // Merge assets    $assets = array_merge($this->assets, $assets);    // Remove duplicates    $this->assets = array_keys(array_flip($assets));  }  /**   * Add assets to the queue of MediaEmbed plugin   *   * Alias for `addAssets`   *   * @param array   $assets An array of assets to add.   * @param boolean $append Append assets to array or reset assets.   */  public function add($assets, $append = true)  {    return $this->addAssets($assets, $append);  }  /**   * Add assets to the queue of MediaEmbed plugin   *   * Alias for `addAssets`   *   * @param array   $assets An array of assets to add.   * @param boolean $append Append assets to array or reset assets.   */  public function addCss($assets, $append = true)  {    return $this->addAssets($assets, $append);  }  /**   * Add assets to the queue of MediaEmbed plugin   *   * Alias for `addAssets`   *   * @param array   $assets An array of assets to add.   * @param boolean $append Append assets to array or reset assets.   */  public function addJs($assets, $append = true)  {    return $this->addAssets($assets, $append);  }  /** -------------------------------   * Private/protected helper methods   * --------------------------------   */  /**   * Get cached media or media with key.   *   * @param  string $key The key to load from the cache.   * @return mixed       The media content.   */  protected function getCachedMedia($key)  {    /** @var Cache $cache */    $cache = $grav['cache'];    // Check, if cache should be used or not    if ($this->config->get('cache.enabled')) {      // Get cache id and try to fetch data      $cache_id = md5('mediaembed' . $key . $cache->getKey());      $data = $cache->fetch($cache_id);      if ((false === $data) || (time() > $data['expire'])) {        // Pack and provide data with a time stamp.        $data = array(          'content' => $this->service->embed($key),          'expire' => time() + $this->config->get('cache.lifetime'),        );        $cache->save($cache_id, $data);      }      // Return data contents      $content = $data['content'];    } else {      // Just call callback and return result      $content = $this->service->embed($key);    }    return $content;  }  protected function parseUrl($url)  {    if (!filter_var($url, FILTER_VALIDATE_URL)) {      return [];    }    // Parse URL    $url = html_entity_decode($url, ENT_COMPAT | ENT_HTML401, 'UTF-8');    $parts = parse_url($url);    $parts['url'] = $url;    // Get top-level domain from URL    $parts['domain'] = isset($parts['host']) ? $parts['host'] : '';    if ( preg_match('~(?P<domain>[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$~i', $parts['domain'], $match) ) {      $parts['domain'] = $match['domain'];    }    if (isset($parts['query'])) {      parse_str(urldecode($parts['query']), $parts['query']);    }    $parts['query'] = [];    return $parts;  }  /**   * Reset MathJax class   */  protected function reset()  {    $this->hashes = [];  }  /**   * Hash a given text.   *   * Called whenever a tag must be hashed when a function insert an   * atomic element in the text stream. Passing $text to through this   * function gives a unique text-token which will be reverted back when   * calling unhash.   *   * @param  string $text The text to be hashed   * @param  string $type The type (category) the text should be saved   *   * @return string       Return a unique text-token which will be   *                      reverted back when calling unhash.   */  protected function hash($text, $data = [])  {    static $counter = 0;    // Swap back any tag hash found in $text so we do not have to `unhash`    // multiple times at the end.    $text = $this->unhash($text);    // Then hash the block    $key = implode('::', array('mediaembed', $this->id, ++$counter, 'M'));    $this->hashes[$key] = [$text, $data];    // String that will replace the tag    return $key;  }  /**   * Swap back in all the tags hashed by hash.   *   * @param  string $text The text to be un-hashed   *   * @return string       A text containing no hash inside   */  protected function unhash($text)  {    $text = preg_replace_callback(      '~mediaembed::([0-9a-z]+)::([0-9]+)::M~i', function($atches) {      return $this->hashes[$matches[0]][0];    }, $text);    return $text;  }}
 |