]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/Services/oEmbed.php
Merge branch '0.7.x' into 0.8.x
[quix0rs-gnu-social.git] / extlib / Services / oEmbed.php
1 <?php
2
3 /**
4  * An interface for oEmbed consumption
5  *
6  * PHP version 5.1.0+
7  *
8  * Copyright (c) 2008, Digg.com, Inc.
9  * 
10  * All rights reserved.
11  * 
12  * Redistribution and use in source and binary forms, with or without 
13  * modification, are permitted provided that the following conditions are met:
14  *
15  *  - Redistributions of source code must retain the above copyright notice,
16  *    this list of conditions and the following disclaimer.
17  *  - Redistributions in binary form must reproduce the above copyright notice,
18  *    this list of conditions and the following disclaimer in the documentation
19  *    and/or other materials provided with the distribution.
20  *  - Neither the name of Digg.com, Inc. nor the names of its contributors 
21  *    may be used to endorse or promote products derived from this software 
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
25  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
27  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
28  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
29  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
30  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
32  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
33  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
34  * POSSIBILITY OF SUCH DAMAGE.
35  *
36  * @category  Services
37  * @package   Services_oEmbed
38  * @author    Joe Stump <joe@joestump.net> 
39  * @copyright 2008 Digg.com, Inc.
40  * @license   http://tinyurl.com/42zef New BSD License
41  * @version   SVN: @version@
42  * @link      http://code.google.com/p/digg
43  * @link      http://oembed.com
44  */
45
46 require_once 'Validate.php';
47 require_once 'Net/URL2.php';
48 require_once 'HTTP/Request.php';
49 require_once 'Services/oEmbed/Exception.php';
50 require_once 'Services/oEmbed/Exception/NoSupport.php';
51 require_once 'Services/oEmbed/Object.php';
52
53 /**
54  * Base class for consuming oEmbed objects
55  *
56  * <code>
57  * <?php
58  * 
59  * require_once 'Services/oEmbed.php';
60  *
61  * // The URL that we'd like to find out more information about.
62  * $url = 'http://flickr.com/photos/joestump/2848795611/';
63  *
64  * // The oEmbed API URI. Not all providers support discovery yet so we're
65  * // explicitly providing one here. If one is not provided Services_oEmbed
66  * // attempts to discover it. If none is found an exception is thrown.
67  * $oEmbed = new Services_oEmbed($url, array(
68  *     Services_oEmbed::OPTION_API => 'http://www.flickr.com/services/oembed/'
69  * ));
70  * $object = $oEmbed->getObject();
71  *
72  * // All of the objects have somewhat sane __toString() methods that allow
73  * // you to output them directly.
74  * echo (string)$object;
75  * 
76  * ?>
77  * </code> 
78  * 
79  * @category  Services
80  * @package   Services_oEmbed
81  * @author    Joe Stump <joe@joestump.net> 
82  * @copyright 2008 Digg.com, Inc.
83  * @license   http://tinyurl.com/42zef New BSD License
84  * @version   Release: @version@
85  * @link      http://code.google.com/p/digg
86  * @link      http://oembed.com
87  */
88 class Services_oEmbed
89 {
90     /**
91      * HTTP timeout in seconds
92      * 
93      * All HTTP requests made by Services_oEmbed will respect this timeout. 
94      * This can be passed to {@link Services_oEmbed::setOption()} or to the
95      * options parameter in {@link Services_oEmbed::__construct()}.
96      * 
97      * @var string OPTION_TIMEOUT Timeout in seconds 
98      */
99     const OPTION_TIMEOUT = 'http_timeout';
100
101     /**
102      * HTTP User-Agent 
103      *
104      * All HTTP requests made by Services_oEmbed will be sent with the 
105      * string set by this option.
106      *
107      * @var string OPTION_USER_AGENT The HTTP User-Agent string
108      */
109     const OPTION_USER_AGENT = 'http_user_agent';
110
111     /**
112      * The API's URI
113      *
114      * If the API is known ahead of time this option can be used to explicitly
115      * set it. If not present then the API is attempted to be discovered 
116      * through the auto-discovery mechanism.
117      *
118      * @var string OPTION_API
119      */
120     const OPTION_API = 'oembed_api';
121
122     /**
123      * Options for oEmbed requests
124      *
125      * @var array $options The options for making requests
126      */
127     protected $options = array(
128         self::OPTION_TIMEOUT    => 3,
129         self::OPTION_API        => null,
130         self::OPTION_USER_AGENT => 'Services_oEmbed 0.1.0'
131     );
132
133     /**
134      * URL of object to get embed information for
135      *
136      * @var object $url {@link Net_URL2} instance of URL of object
137      */
138     protected $url = null;
139
140     /**
141      * Constructor
142      *
143      * @param string $url     The URL to fetch an oEmbed for
144      * @param array  $options A list of options for the oEmbed lookup
145      *
146      * @throws {@link Services_oEmbed_Exception} if the $url is invalid
147      * @throws {@link Services_oEmbed_Exception} when no valid API is found
148      * @return void
149      */
150     public function __construct($url, array $options = array())
151     {
152         if (Validate::uri($url)) {
153             $this->url = new Net_URL2($url);
154         } else {
155             throw new Services_oEmbed_Exception('URL is invalid');
156         }
157
158         if (count($options)) {
159             foreach ($options as $key => $val) {
160                 $this->setOption($key, $val);
161             }
162         }
163
164         if ($this->options[self::OPTION_API] === null) {
165             $this->options[self::OPTION_API] = $this->discover();
166         } 
167     }
168
169     /**
170      * Set an option for the oEmbed request
171      * 
172      * @param mixed $option The option name
173      * @param mixed $value  The option value
174      *
175      * @see Services_oEmbed::OPTION_API, Services_oEmbed::OPTION_TIMEOUT
176      * @throws {@link Services_oEmbed_Exception} on invalid option
177      * @access public
178      * @return void
179      */
180     public function setOption($option, $value)
181     {
182         switch ($option) {
183         case self::OPTION_API:
184         case self::OPTION_TIMEOUT:
185             break;
186         default:
187             throw new Services_oEmbed_Exception(
188                 'Invalid option "' . $option . '"'
189             );
190         }
191
192         $func = '_set_' . $option;
193         if (method_exists($this, $func)) {
194             $this->options[$option] = $this->$func($value);
195         } else {
196             $this->options[$option] = $value;
197         }
198     }
199
200     /**
201      * Set the API option
202      * 
203      * @param string $value The API's URI
204      *
205      * @throws {@link Services_oEmbed_Exception} on invalid API URI
206      * @see Validate::uri()
207      * @return string
208      */
209     protected function _set_oembed_api($value)
210     {
211         if (!Validate::uri($value)) {
212             throw new Services_oEmbed_Exception(
213                 'API URI provided is invalid'
214             );
215         }
216
217         return $value;
218     }
219
220     /**
221      * Get the oEmbed response
222      *
223      * @param array $params Optional parameters for 
224      * 
225      * @throws {@link Services_oEmbed_Exception} on cURL errors
226      * @throws {@link Services_oEmbed_Exception} on HTTP errors
227      * @throws {@link Services_oEmbed_Exception} when result is not parsable 
228      * @return object The oEmbed response as an object
229      */
230     public function getObject(array $params = array())
231     {
232         $params['url'] = $this->url->getURL();
233         if (!isset($params['format'])) {
234             $params['format'] = 'json';
235         }
236
237         $sets = array();
238         foreach ($params as $var => $val) {
239             $sets[] = $var . '=' . urlencode($val);
240         }
241
242         $url = $this->options[self::OPTION_API] . '?' . implode('&', $sets);
243
244         $ch = curl_init();
245         curl_setopt($ch, CURLOPT_URL, $url);
246         curl_setopt($ch, CURLOPT_HEADER, false);
247         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
248         curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->options[self::OPTION_TIMEOUT]);
249         $result = curl_exec($ch);
250
251         if (curl_errno($ch)) {
252             throw new Services_oEmbed_Exception(
253                 curl_error($ch), curl_errno($ch)
254             );
255         }
256
257         $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
258         if (substr($code, 0, 1) != '2') {
259             throw new Services_oEmbed_Exception('Non-200 code returned');
260         }
261
262         curl_close($ch);
263
264         switch ($params['format']) {
265         case 'json':
266             $res = json_decode($result);
267             if (!is_object($res)) {
268                 throw new Services_oEmbed_Exception(
269                     'Could not parse JSON response'
270                 );
271             }
272             break;
273         case 'xml':
274             libxml_use_internal_errors(true);
275             $res = simplexml_load_string($result);
276             if (!$res instanceof SimpleXMLElement) {
277                 $errors = libxml_get_errors();
278                 $err    = array_shift($errors);
279                 libxml_clear_errors();
280                 libxml_use_internal_errors(false);
281                 throw new Services_oEmbed_Exception(
282                     $err->message, $error->code
283                 );
284             }
285             break;
286         }
287
288         return Services_oEmbed_Object::factory($res);
289     }
290
291     /**
292      * Discover an oEmbed API 
293      *
294      * @param string $url The URL to attempt to discover oEmbed for
295      *
296      * @throws {@link Services_oEmbed_Exception} if the $url is invalid
297      * @return string The oEmbed API endpoint discovered
298      */
299     protected function discover($url)
300     {
301         $body = $this->sendRequest($url);
302
303         // Find all <link /> tags that have a valid oembed type set. We then
304         // extract the href attribute for each type.
305         $regexp = '#<link([^>]*)type="' . 
306                   '(application/json|text/xml)\+oembed"([^>]*)>#i';
307
308         $m = $ret = array();
309         if (!preg_match_all($regexp, $body, $m)) {
310             throw new Services_oEmbed_Exception_NoSupport(
311                 'No valid oEmbed links found on page'
312             );
313         }
314
315         foreach ($m[0] as $i => $link) {
316             $h = array();
317             if (preg_match('/href="([^"]+)"/i', $link, $h)) {
318                 $ret[$m[2][$i]] = $h[1];
319             }
320         } 
321
322         return (isset($ret['json']) ? $ret['json'] : array_pop($ret));
323     }
324
325     /**
326      * Send a GET request to the provider
327      * 
328      * @param mixed $url The URL to send the request to
329      *
330      * @throws {@link Services_oEmbed_Exception} on HTTP errors
331      * @return string The contents of the response
332      */
333     private function sendRequest($url)
334     {
335         $ch = curl_init();
336         curl_setopt($ch, CURLOPT_URL, $url);
337         curl_setopt($ch, CURLOPT_HEADER, false);
338         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
339         curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->options[self::OPTION_TIMEOUT]);
340         curl_setopt($ch, CURLOPT_USERAGENT, $this->options[self::OPTION_USER_AGENT]);
341         $result = curl_exec($ch);
342         if (curl_errno($ch)) {
343             throw new Services_oEmbed_Exception(
344                 curl_error($ch), curl_errno($ch)
345             );
346         }
347
348         $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
349         if (substr($code, 0, 1) != '2') {
350             throw new Services_oEmbed_Exception('Non-200 code returned');
351         }
352
353         return $result;
354     }
355 }
356
357 ?>