4 * An interface for oEmbed consumption
8 * Copyright (c) 2008, Digg.com, Inc.
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions are met:
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.
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.
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
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';
54 * Base class for consuming oEmbed objects
59 * require_once 'Services/oEmbed.php';
61 * // The URL that we'd like to find out more information about.
62 * $url = 'http://flickr.com/photos/joestump/2848795611/';
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/'
70 * $object = $oEmbed->getObject();
72 * // All of the objects have somewhat sane __toString() methods that allow
73 * // you to output them directly.
74 * echo (string)$object;
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
91 * HTTP timeout in seconds
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()}.
97 * @var string OPTION_TIMEOUT Timeout in seconds
99 const OPTION_TIMEOUT = 'http_timeout';
104 * All HTTP requests made by Services_oEmbed will be sent with the
105 * string set by this option.
107 * @var string OPTION_USER_AGENT The HTTP User-Agent string
109 const OPTION_USER_AGENT = 'http_user_agent';
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.
118 * @var string OPTION_API
120 const OPTION_API = 'oembed_api';
123 * Options for oEmbed requests
125 * @var array $options The options for making requests
127 protected $options = array(
128 self::OPTION_TIMEOUT => 3,
129 self::OPTION_API => null,
130 self::OPTION_USER_AGENT => 'Services_oEmbed 0.1.0'
134 * URL of object to get embed information for
136 * @var object $url {@link Net_URL2} instance of URL of object
138 protected $url = null;
143 * @param string $url The URL to fetch an oEmbed for
144 * @param array $options A list of options for the oEmbed lookup
146 * @throws {@link Services_oEmbed_Exception} if the $url is invalid
147 * @throws {@link Services_oEmbed_Exception} when no valid API is found
150 public function __construct($url, array $options = array())
152 if (Validate::uri($url)) {
153 $this->url = new Net_URL2($url);
155 throw new Services_oEmbed_Exception('URL is invalid');
158 if (count($options)) {
159 foreach ($options as $key => $val) {
160 $this->setOption($key, $val);
164 if ($this->options[self::OPTION_API] === null) {
165 $this->options[self::OPTION_API] = $this->discover($url);
170 * Set an option for the oEmbed request
172 * @param mixed $option The option name
173 * @param mixed $value The option value
175 * @see Services_oEmbed::OPTION_API, Services_oEmbed::OPTION_TIMEOUT
176 * @throws {@link Services_oEmbed_Exception} on invalid option
180 public function setOption($option, $value)
183 case self::OPTION_API:
184 case self::OPTION_TIMEOUT:
187 throw new Services_oEmbed_Exception(
188 'Invalid option "' . $option . '"'
192 $func = '_set_' . $option;
193 if (method_exists($this, $func)) {
194 $this->options[$option] = $this->$func($value);
196 $this->options[$option] = $value;
203 * @param string $value The API's URI
205 * @throws {@link Services_oEmbed_Exception} on invalid API URI
206 * @see Validate::uri()
209 protected function _set_oembed_api($value)
211 if (!Validate::uri($value)) {
212 throw new Services_oEmbed_Exception(
213 'API URI provided is invalid'
221 * Get the oEmbed response
223 * @param array $params Optional parameters for
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
230 public function getObject(array $params = array())
232 $params['url'] = $this->url->getURL();
233 if (!isset($params['format'])) {
234 $params['format'] = 'json';
238 foreach ($params as $var => $val) {
239 $sets[] = $var . '=' . urlencode($val);
242 $url = $this->options[self::OPTION_API] . '?' . implode('&', $sets);
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);
251 if (curl_errno($ch)) {
252 throw new Services_oEmbed_Exception(
253 curl_error($ch), curl_errno($ch)
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');
264 switch ($params['format']) {
266 $res = json_decode($result);
267 if (!is_object($res)) {
268 throw new Services_oEmbed_Exception(
269 'Could not parse JSON response'
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
288 return Services_oEmbed_Object::factory($res);
292 * Discover an oEmbed API
294 * @param string $url The URL to attempt to discover oEmbed for
296 * @throws {@link Services_oEmbed_Exception} if the $url is invalid
297 * @return string The oEmbed API endpoint discovered
299 protected function discover($url)
301 $body = $this->sendRequest($url);
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';
309 if (!preg_match_all($regexp, $body, $m)) {
310 throw new Services_oEmbed_Exception_NoSupport(
311 'No valid oEmbed links found on page'
315 foreach ($m[0] as $i => $link) {
317 if (preg_match('/href="([^"]+)"/i', $link, $h)) {
318 $ret[$m[2][$i]] = $h[1];
322 return (isset($ret['application/json']) ? $ret['application/json'] : array_pop($ret));
326 * Send a GET request to the provider
328 * @param mixed $url The URL to send the request to
330 * @throws {@link Services_oEmbed_Exception} on HTTP errors
331 * @return string The contents of the response
333 private function sendRequest($url)
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)
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');