PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/extlib/Services/oEmbed.php

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