]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - lib/oembedhelper.php
Merge remote-tracking branch '1.0.x' into lists_fixes
[quix0rs-gnu-social.git] / lib / oembedhelper.php
index ef2b59214c4ebb170bc9f72eef72ae2799ac2840..6d8064e660502298525ecfb205d40f0c6a41ed25 100644 (file)
 if (!defined('STATUSNET')) {
     exit(1);
 }
-require_once INSTALLDIR.'/extlib/Services/oEmbed.php';
 
 
 /**
- * Utility class to wrap Services_oEmbed:
+ * Utility class to wrap basic oEmbed lookups.
  *
  * Blacklisted hosts will use an alternate lookup method:
  *  - Twitpic
@@ -44,6 +43,13 @@ class oEmbedHelper
     protected static $apiMap = array(
         'flickr.com' => 'http://www.flickr.com/services/oembed/',
         'yfrog.com' => 'http://www.yfrog.com/api/oembed',
+        'youtube.com' => 'http://www.youtube.com/oembed',
+        'viddler.com' => 'http://lab.viddler.com/services/oembed/',
+        'qik.com' => 'http://qik.com/api/oembed.json',
+        'revision3.com' => 'http://revision3.com/api/oembed/',
+        'hulu.com' => 'http://www.hulu.com/api/oembed.json',
+        'vimeo.com' => 'http://www.vimeo.com/api/oembed.json',
+        'my.opera.com' => 'http://my.opera.com/service/oembed',
     );
     protected static $functionMap = array(
         'twitpic.com' => 'oEmbedHelper::twitPic',
@@ -70,62 +76,178 @@ class oEmbedHelper
      */
     public static function getObject($url, $params=array())
     {
-        common_log(LOG_INFO, 'QQQ: wtf? ' . $url);
         $host = parse_url($url, PHP_URL_HOST);
         if (substr($host, 0, 4) == 'www.') {
             $host = substr($host, 4);
         }
 
-        // Blacklist: systems with no oEmbed API of their own, which are
-        // either missing from or broken on oohembed.com's proxy.
-        // we know how to look data up in another way...
-        if (array_key_exists($host, self::$functionMap)) {
-            $func = self::$functionMap[$host];
-            return call_user_func($func, $url, $params);
+        common_log(LOG_INFO, 'Checking for oEmbed data for ' . $url);
+
+        // You can fiddle with the order of discovery -- either skipping
+        // some types or re-ordering them.
+
+        $order = common_config('oembed', 'order');
+
+        foreach ($order as $method) {
+
+            switch ($method) {
+            case 'built-in':
+                common_log(LOG_INFO, 'Considering built-in oEmbed methods...');
+                // Blacklist: systems with no oEmbed API of their own, which are
+                // either missing from or broken on oohembed.com's proxy.
+                // we know how to look data up in another way...
+                if (array_key_exists($host, self::$functionMap)) {
+                    common_log(LOG_INFO, 'We have a built-in method for ' . $host);
+                    $func = self::$functionMap[$host];
+                    return call_user_func($func, $url, $params);
+                }
+                break;
+            case 'well-known':
+                common_log(LOG_INFO, 'Considering well-known oEmbed endpoints...');
+                // Whitelist: known API endpoints for sites that don't provide discovery...
+                if (array_key_exists($host, self::$apiMap)) {
+                    $api = self::$apiMap[$host];
+                    common_log(LOG_INFO, 'Using well-known endpoint "' . $api . '" for "' . $host . '"');
+                    break 2;
+                }
+                break;
+            case 'discovery':
+                try {
+                    common_log(LOG_INFO, 'Trying to discover an oEmbed endpoint using link headers.');
+                    $api = self::discover($url);
+                    common_log(LOG_INFO, 'Found API endpoint ' . $api . ' for URL ' . $url);
+                    break 2;
+                } catch (Exception $e) {
+                    common_log(LOG_INFO, 'Could not find an oEmbed endpoint using link headers.');
+                    // Just ignore it!
+                }
+                break;
+            case 'service':
+                $api = common_config('oembed', 'endpoint');
+                common_log(LOG_INFO, 'Using service API endpoint ' . $api);
+                break 2;
+                break;
+            }
         }
 
-        // Whitelist: known API endpoints for sites that don't provide discovery...
-        if (array_key_exists($host, self::$apiMap)) {
-            $api = self::$apiMap[$host];
-            common_log(LOG_INFO, 'QQQ: going to: ' . $api);
-        } else {
-            $api = false;
-            common_log(LOG_INFO, 'QQQ: no map for ' . $host);
+        if (empty($api)) {
+            // TRANS: Server exception thrown in oEmbed action if no API endpoint is available.
+            throw new ServerException(_('No oEmbed API endpoint available.'));
         }
+
         return self::getObjectFrom($api, $url, $params);
     }
 
     /**
-     * Actually do an oEmbed lookup to a particular API endpoint,
-     * or to the autodiscovered target, or to oohembed.
+     * Perform basic discovery.
+     * @return string
+     */
+    static function discover($url)
+    {
+        // @fixme ideally skip this for non-HTML stuff!
+        $body = self::http($url);
+        return self::discoverFromHTML($url, $body);
+    }
+
+    /**
+     * Partially ripped from OStatus' FeedDiscovery class.
+     *
+     * @param string $url source URL, used to resolve relative links
+     * @param string $body HTML body text
+     * @return mixed string with URL or false if no target found
+     */
+    static function discoverFromHTML($url, $body)
+    {
+        // DOMDocument::loadHTML may throw warnings on unrecognized elements,
+        // and notices on unrecognized namespaces.
+        $old = error_reporting(error_reporting() & ~(E_WARNING | E_NOTICE));
+        $dom = new DOMDocument();
+        $ok = $dom->loadHTML($body);
+        error_reporting($old);
+
+        if (!$ok) {
+            throw new oEmbedHelper_BadHtmlException();
+        }
+
+        // Ok... now on to the links!
+        $feeds = array(
+            'application/json+oembed' => false,
+        );
+
+        $nodes = $dom->getElementsByTagName('link');
+        for ($i = 0; $i < $nodes->length; $i++) {
+            $node = $nodes->item($i);
+            if ($node->hasAttributes()) {
+                $rel = $node->attributes->getNamedItem('rel');
+                $type = $node->attributes->getNamedItem('type');
+                $href = $node->attributes->getNamedItem('href');
+                if ($rel && $type && $href) {
+                    $rel = array_filter(explode(" ", $rel->value));
+                    $type = trim($type->value);
+                    $href = trim($href->value);
+
+                    if (in_array('alternate', $rel) && array_key_exists($type, $feeds) && empty($feeds[$type])) {
+                        // Save the first feed found of each type...
+                        $feeds[$type] = $href;
+                    }
+                }
+            }
+        }
+
+        // Return the highest-priority feed found
+        foreach ($feeds as $type => $url) {
+            if ($url) {
+                return $url;
+            }
+        }
+
+        throw new oEmbedHelper_DiscoveryException();
+    }
+
+    /**
+     * Actually do an oEmbed lookup to a particular API endpoint.
      *
-     * @param mixed $api string or false: oEmbed API endpoint URL
+     * @param string $api oEmbed API endpoint URL
      * @param string $url target URL to look up info about
      * @param array $params
      * @return object
      */
-    static protected function getObjectFrom($api, $url, $params=array())
+    static function getObjectFrom($api, $url, $params=array())
     {
-        $options = array();
-        if ($api) {
-            $options[Services_oEmbed::OPTION_API] = $api;
+        $params['url'] = $url;
+        $params['format'] = 'json';
+        $data = self::json($api, $params);
+        return self::normalize($data);
+    }
+
+    /**
+     * Normalize oEmbed format.
+     *
+     * @param object $orig
+     * @return object
+     */
+    static function normalize($orig)
+    {
+        $data = clone($orig);
+
+        if (empty($data->type)) {
+            throw new Exception('Invalid oEmbed data: no type field.');
         }
 
-        try {
-            $oEmbed = new Services_oEmbed_Tweaked($url, $options);
-        } catch (Services_oEmbed_Exception_NoSupport $e) {
-            // Discovery failed... fall back to oohembed if enabled.
-            $oohembed = common_config('oohembed', 'endpoint');
-            if ($oohembed) {
-                $options[Services_oEmbed::OPTION_API] = $oohembed;
-                $oEmbed = new Services_oEmbed_Tweaked($url, $options);
-            } else {
-                throw $e;
+        if ($data->type == 'image') {
+            // YFrog does this.
+            $data->type = 'photo';
+        }
+
+        if (isset($data->thumbnail_url)) {
+            if (!isset($data->thumbnail_width)) {
+                // !?!?!
+                $data->thumbnail_width = common_config('attachments', 'thumb_width');
+                $data->thumbnail_height = common_config('attachments', 'thumb_height');
             }
         }
 
-        // And.... let's go look it up!
-        return $oEmbed->getObject($params);
+        return $data;
     }
 
     /**
@@ -212,35 +334,26 @@ class oEmbedHelper
     }
 }
 
-class Services_oEmbed_Tweaked extends Services_oEmbed
+class oEmbedHelper_Exception extends Exception
 {
-    protected function discover($url)
+    public function __construct($message = "", $code = 0, $previous = null)
     {
-        $api = parent::discover($url);
-        if (strpos($api, '?') !== false) {
-            // Services_oEmbed doesn't expect to find existing params
-            // on its API endpoint, which may surprise you since the
-            // spec says discovery URLs should include parameters... :)
-            //
-            // Appending a '&' on the end keeps the later-appended '?'
-            // from breaking whatever the first parameters was.
-            return $api . '&';
-        }
-        return $api;
+        parent::__construct($message, $code);
     }
+}
 
-    public function getObject(array $params = array())
+class oEmbedHelper_BadHtmlException extends oEmbedHelper_Exception
+{
+    function __construct($previous=null)
     {
-        $api = $this->options[self::OPTION_API];
-        if (strpos($api, '?') !== false) {
-            // The Services_oEmbed code appends a '?' on the end, which breaks
-            // the next parameter which may be something important like
-            // maxwidth.
-            //
-            // Sticking this bogus entry into our parameters gets us past it.
-            $params = array_merge(array('statusnet' => 1), $params);
-        }
-        return parent::getObject($params);
+        return parent::__construct('Bad HTML in discovery data.', 0, $previous);
     }
+}
 
-}
\ No newline at end of file
+class oEmbedHelper_DiscoveryException extends oEmbedHelper_Exception
+{
+    function __construct($previous=null)
+    {
+        return parent::__construct('No oEmbed discovery data.', 0, $previous);
+    }
+}