]> git.mxchange.org Git - friendica.git/blob - src/Content/OEmbed.php
Replace dba::select(limit => 1) by dba::selectOne
[friendica.git] / src / Content / OEmbed.php
1 <?php\r
2 \r
3 /**\r
4  * @file src/Content/OEmbed.php\r
5  */\r
6 \r
7 namespace Friendica\Content;\r
8 \r
9 use Friendica\Core\Cache;\r
10 use Friendica\Core\System;\r
11 use Friendica\Core\Config;\r
12 use Friendica\Database\DBM;\r
13 use Friendica\Util\ParseUrl;\r
14 use dba;\r
15 use DOMDocument;\r
16 use DOMXPath;\r
17 use DOMNode;\r
18 use Exception;\r
19 \r
20 require_once 'include/dba.php';\r
21 require_once 'mod/proxy.php';\r
22 \r
23 /**\r
24  * Handles all OEmbed content fetching and replacement\r
25  *\r
26  * OEmbed is a standard used to allow an embedded representation of a URL on\r
27  * third party sites\r
28  *\r
29  * @see https://oembed.com\r
30  *\r
31  * @author Hypolite Petovan <mrpetovan@gmail.com>\r
32  */\r
33 class OEmbed\r
34 {\r
35         public static function replaceCallback($matches)\r
36         {\r
37                 $embedurl = $matches[1];\r
38                 $j = self::fetchURL($embedurl);\r
39                 $s = self::formatObject($j);\r
40 \r
41                 return $s;\r
42         }\r
43 \r
44         /**\r
45          * @brief Get data from an URL to embed its content.\r
46          *\r
47          * @param string $embedurl The URL from which the data should be fetched.\r
48          * @param bool $no_rich_type If set to true rich type content won't be fetched.\r
49          *\r
50          * @return bool|object Returns object with embed content or false if no embedable\r
51          *       content exists\r
52          */\r
53         public static function fetchURL($embedurl, $no_rich_type = false)\r
54         {\r
55                 $embedurl = trim($embedurl, "'");\r
56                 $embedurl = trim($embedurl, '"');\r
57 \r
58                 $a = get_app();\r
59 \r
60                 $condition = ['url' => normalise_link($embedurl)];\r
61                 $r = dba::selectOne('oembed', ['content'], $condition);\r
62                 if (DBM::is_result($r)) {\r
63                         $txt = $r["content"];\r
64                 } else {\r
65                         $txt = Cache::get($a->videowidth . $embedurl);\r
66                 }\r
67                 // These media files should now be caught in bbcode.php\r
68                 // left here as a fallback in case this is called from another source\r
69 \r
70                 $noexts = array("mp3", "mp4", "ogg", "ogv", "oga", "ogm", "webm");\r
71                 $ext = pathinfo(strtolower($embedurl), PATHINFO_EXTENSION);\r
72 \r
73 \r
74                 if (is_null($txt)) {\r
75                         $txt = "";\r
76 \r
77                         if (!in_array($ext, $noexts)) {\r
78                                 // try oembed autodiscovery\r
79                                 $redirects = 0;\r
80                                 $html_text = fetch_url($embedurl, false, $redirects, 15, "text/*");\r
81                                 if ($html_text) {\r
82                                         $dom = @DOMDocument::loadHTML($html_text);\r
83                                         if ($dom) {\r
84                                                 $xpath = new DOMXPath($dom);\r
85                                                 $entries = $xpath->query("//link[@type='application/json+oembed']");\r
86                                                 foreach ($entries as $e) {\r
87                                                         $href = $e->getAttributeNode("href")->nodeValue;\r
88                                                         $txt = fetch_url($href . '&maxwidth=' . $a->videowidth);\r
89                                                         break;\r
90                                                 }\r
91                                                 $entries = $xpath->query("//link[@type='text/json+oembed']");\r
92                                                 foreach ($entries as $e) {\r
93                                                         $href = $e->getAttributeNode("href")->nodeValue;\r
94                                                         $txt = fetch_url($href . '&maxwidth=' . $a->videowidth);\r
95                                                         break;\r
96                                                 }\r
97                                         }\r
98                                 }\r
99                         }\r
100 \r
101                         $txt = trim($txt);\r
102 \r
103                         if (!$txt || $txt[0] != "{") {\r
104                                 $txt = '{"type":"error"}';\r
105                         } else { //save in cache\r
106                                 $j = json_decode($txt);\r
107                                 if ($j->type != "error") {\r
108                                         dba::insert('oembed', array('url' => normalise_link($embedurl),\r
109                                                 'content' => $txt, 'created' => datetime_convert()), true);\r
110                                 }\r
111 \r
112                                 Cache::set($a->videowidth . $embedurl, $txt, CACHE_DAY);\r
113                         }\r
114                 }\r
115 \r
116                 $j = json_decode($txt);\r
117 \r
118                 if (!is_object($j)) {\r
119                         return false;\r
120                 }\r
121 \r
122                 // Always embed the SSL version\r
123                 if (isset($j->html)) {\r
124                         $j->html = str_replace(array("http://www.youtube.com/", "http://player.vimeo.com/"), array("https://www.youtube.com/", "https://player.vimeo.com/"), $j->html);\r
125                 }\r
126 \r
127                 $j->embedurl = $embedurl;\r
128 \r
129                 // If fetching information doesn't work, then improve via internal functions\r
130                 if (($j->type == "error") || ($no_rich_type && ($j->type == "rich"))) {\r
131                         $data = ParseUrl::getSiteinfoCached($embedurl, true, false);\r
132                         $j->type = $data["type"];\r
133 \r
134                         if ($j->type == "photo") {\r
135                                 $j->url = $data["url"];\r
136                                 //$j->width = $data["images"][0]["width"];\r
137                                 //$j->height = $data["images"][0]["height"];\r
138                         }\r
139 \r
140                         if (isset($data["title"])) {\r
141                                 $j->title = $data["title"];\r
142                         }\r
143 \r
144                         if (isset($data["text"])) {\r
145                                 $j->description = $data["text"];\r
146                         }\r
147 \r
148                         if (is_array($data["images"])) {\r
149                                 $j->thumbnail_url = $data["images"][0]["src"];\r
150                                 $j->thumbnail_width = $data["images"][0]["width"];\r
151                                 $j->thumbnail_height = $data["images"][0]["height"];\r
152                         }\r
153                 }\r
154 \r
155                 call_hooks('oembed_fetch_url', $embedurl, $j);\r
156 \r
157                 return $j;\r
158         }\r
159 \r
160         public static function formatObject($j)\r
161         {\r
162                 $embedurl = $j->embedurl;\r
163                 $jhtml = $j->html;\r
164                 $ret = '<div class="oembed ' . $j->type . '">';\r
165                 switch ($j->type) {\r
166                         case "video":\r
167                                 if (isset($j->thumbnail_url)) {\r
168                                         $tw = (isset($j->thumbnail_width) && intval($j->thumbnail_width)) ? $j->thumbnail_width : 200;\r
169                                         $th = (isset($j->thumbnail_height) && intval($j->thumbnail_height)) ? $j->thumbnail_height : 180;\r
170                                         // make sure we don't attempt divide by zero, fallback is a 1:1 ratio\r
171                                         $tr = (($th) ? $tw / $th : 1);\r
172 \r
173                                         $th = 120;\r
174                                         $tw = $th * $tr;\r
175                                         $tpl = get_markup_template('oembed_video.tpl');\r
176                                         $ret .= replace_macros($tpl, array(\r
177                                                 '$baseurl' => System::baseUrl(),\r
178                                                 '$embedurl' => $embedurl,\r
179                                                 '$escapedhtml' => base64_encode($jhtml),\r
180                                                 '$tw' => $tw,\r
181                                                 '$th' => $th,\r
182                                                 '$turl' => $j->thumbnail_url,\r
183                                         ));\r
184                                 } else {\r
185                                         $ret = $jhtml;\r
186                                 }\r
187                                 break;\r
188                         case "photo":\r
189                                 $ret .= '<img width="' . $j->width . '" src="' . proxy_url($j->url) . '">';\r
190                                 break;\r
191                         case "link":\r
192                                 break;\r
193                         case "rich":\r
194                                 if (self::isAllowedURL($embedurl)) {\r
195                                         $ret .= proxy_parse_html($jhtml);\r
196                                 }\r
197                                 break;\r
198                 }\r
199 \r
200                 $ret .= '</div>';\r
201                 // add link to source if not present in "rich" type\r
202                 if ($j->type != 'rich' || !strpos($j->html, $embedurl)) {\r
203                         $ret .= '<h4>';\r
204                         if (isset($j->title)) {\r
205                                 if (isset($j->provider_name)) {\r
206                                         $ret .= $j->provider_name . ": ";\r
207                                 }\r
208 \r
209                                 $embedlink = (isset($j->title)) ? $j->title : $embedurl;\r
210                                 $ret .= '<a href="' . $embedurl . '" rel="oembed">' . $embedlink . '</a>';\r
211                                 if (isset($j->author_name)) {\r
212                                         $ret .= ' (' . $j->author_name . ')';\r
213                                 }\r
214                         } elseif (isset($j->provider_name) || isset($j->author_name)) {\r
215                                 $embedlink = "";\r
216                                 if (isset($j->provider_name)) {\r
217                                         $embedlink .= $j->provider_name;\r
218                                 }\r
219 \r
220                                 if (isset($j->author_name)) {\r
221                                         if ($embedlink != "") {\r
222                                                 $embedlink .= ": ";\r
223                                         }\r
224 \r
225                                         $embedlink .= $j->author_name;\r
226                                 }\r
227                                 if (trim($embedlink) == "") {\r
228                                         $embedlink = $embedurl;\r
229                                 }\r
230 \r
231                                 $ret .= '<a href="' . $embedurl . '" rel="oembed">' . $embedlink . '</a>';\r
232                         }\r
233                         $ret .= "</h4>";\r
234                 } elseif (!strpos($j->html, $embedurl)) {\r
235                         // add <a> for html2bbcode conversion\r
236                         $ret .= '<a href="' . $embedurl . '" rel="oembed">' . $j->title . '</a>';\r
237                 }\r
238 \r
239                 $ret = str_replace("\n", "", $ret);\r
240                 return mb_convert_encoding($ret, 'HTML-ENTITIES', mb_detect_encoding($ret));\r
241         }\r
242 \r
243         public static function BBCode2HTML($text)\r
244         {\r
245                 $stopoembed = Config::get("system", "no_oembed");\r
246                 if ($stopoembed == true) {\r
247                         return preg_replace("/\[embed\](.+?)\[\/embed\]/is", "<!-- oembed $1 --><i>" . t('Embedding disabled') . " : $1</i><!-- /oembed $1 -->", $text);\r
248                 }\r
249                 return preg_replace_callback("/\[embed\](.+?)\[\/embed\]/is", ['self', 'replaceCallback'], $text);\r
250         }\r
251 \r
252         /**\r
253          * Find <span class='oembed'>..<a href='url' rel='oembed'>..</a></span>\r
254          * and replace it with [embed]url[/embed]\r
255          */\r
256         public static function HTML2BBCode($text)\r
257         {\r
258                 // start parser only if 'oembed' is in text\r
259                 if (strpos($text, "oembed")) {\r
260 \r
261                         // convert non ascii chars to html entities\r
262                         $html_text = mb_convert_encoding($text, 'HTML-ENTITIES', mb_detect_encoding($text));\r
263 \r
264                         // If it doesn't parse at all, just return the text.\r
265                         $dom = @DOMDocument::loadHTML($html_text);\r
266                         if (!$dom) {\r
267                                 return $text;\r
268                         }\r
269                         $xpath = new DOMXPath($dom);\r
270 \r
271                         $xattr = self::buildXPath("class", "oembed");\r
272                         $entries = $xpath->query("//div[$xattr]");\r
273 \r
274                         $xattr = "@rel='oembed'"; //oe_build_xpath("rel","oembed");\r
275                         foreach ($entries as $e) {\r
276                                 $href = $xpath->evaluate("a[$xattr]/@href", $e)->item(0)->nodeValue;\r
277                                 if (!is_null($href)) {\r
278                                         $e->parentNode->replaceChild(new DOMText("[embed]" . $href . "[/embed]"), $e);\r
279                                 }\r
280                         }\r
281                         return self::getInnerHTML($dom->getElementsByTagName("body")->item(0));\r
282                 } else {\r
283                         return $text;\r
284                 }\r
285         }\r
286 \r
287         /**\r
288          * Determines if rich content OEmbed is allowed for the provided URL\r
289          *\r
290          * @brief Determines if rich content OEmbed is allowed for the provided URL\r
291          * @param string $url\r
292          * @return boolean\r
293          */\r
294         public static function isAllowedURL($url)\r
295         {\r
296                 if (!Config::get('system', 'no_oembed_rich_content')) {\r
297                         return true;\r
298                 }\r
299 \r
300                 $domain = parse_url($url, PHP_URL_HOST);\r
301                 if (!x($domain)) {\r
302                         return false;\r
303                 }\r
304 \r
305                 $str_allowed = Config::get('system', 'allowed_oembed', '');\r
306                 if (!x($str_allowed)) {\r
307                         return false;\r
308                 }\r
309                 \r
310                 $allowed = explode(',', $str_allowed);\r
311 \r
312                 return allowed_domain($domain, $allowed);\r
313         }\r
314 \r
315         public static function getHTML($url, $title = null)\r
316         {\r
317                 // Always embed the SSL version\r
318                 $url = str_replace(array("http://www.youtube.com/", "http://player.vimeo.com/"),\r
319                                         array("https://www.youtube.com/", "https://player.vimeo.com/"), $url);\r
320 \r
321                 $o = OEmbed::fetchURL($url);\r
322 \r
323                 if (!is_object($o) || $o->type == 'error') {\r
324                         throw new Exception('OEmbed failed for URL: ' . $url);\r
325                 }\r
326 \r
327                 if (x($title)) {\r
328                         $o->title = $title;\r
329                 }\r
330 \r
331                 $html = OEmbed::formatObject($o);\r
332 \r
333                 return $html;\r
334         }\r
335 \r
336         /**\r
337          * @brief Generates the iframe HTML for an oembed attachment.\r
338          *\r
339          * Width and height are given by the remote, and are regularly too small for\r
340          * the generated iframe.\r
341          *\r
342          * The width is entirely discarded for the actual width of the post, while fixed\r
343          * height is used as a starting point before the inevitable resizing.\r
344          *\r
345          * Since the iframe is automatically resized on load, there are no need for ugly\r
346          * and impractical scrollbars.\r
347          *\r
348          * @todo This function is currently unused until someoneā„¢ adds support for a separate OEmbed domain\r
349          *\r
350          * @param string $src Original remote URL to embed\r
351          * @param string $width\r
352          * @param string $height\r
353          * @return string formatted HTML\r
354          *\r
355          * @see oembed_format_object()\r
356          */\r
357         private static function iframe($src, $width, $height)\r
358         {\r
359                 $a = get_app();\r
360 \r
361                 if (!$height || strstr($height, '%')) {\r
362                         $height = '200';\r
363                 }\r
364                 $width = '100%';\r
365 \r
366                 $src = System::baseUrl() . '/oembed/' . base64url_encode($src);\r
367                 return '<iframe onload="resizeIframe(this);" class="embed_rich" height="' . $height . '" width="' . $width . '" src="' . $src . '" allowfullscreen scrolling="no" frameborder="no">' . t('Embedded content') . '</iframe>';\r
368         }\r
369 \r
370         /**\r
371          * Generates an XPath query to select elements whose provided attribute contains\r
372          * the provided value in a space-separated list.\r
373          *\r
374          * @brief Generates attribute search XPath string\r
375          *\r
376          * @param string $attr Name of the attribute to seach\r
377          * @param string $value Value to search in a space-separated list\r
378          * @return string\r
379          */\r
380         private static function buildXPath($attr, $value)\r
381         {\r
382                 // https://www.westhoffswelt.de/blog/2009/6/9/select-html-elements-with-more-than-one-css-class-using-xpath\r
383                 return "contains(normalize-space(@$attr), ' $value ') or substring(normalize-space(@$attr), 1, string-length('$value') + 1) = '$value ' or substring(normalize-space(@$attr), string-length(@$attr) - string-length('$value')) = ' $value' or @$attr = '$value'";\r
384         }\r
385 \r
386         /**\r
387          * Returns the inner XML string of a provided DOMNode\r
388          *\r
389          * @brief Returns the inner XML string of a provided DOMNode\r
390          *\r
391          * @param DOMNode $node\r
392          * @return string\r
393          */\r
394         private static function getInnerHTML(DOMNode $node)\r
395         {\r
396                 $innerHTML = '';\r
397                 $children = $node->childNodes;\r
398                 foreach ($children as $child) {\r
399                         $innerHTML .= $child->ownerDocument->saveXML($child);\r
400                 }\r
401                 return $innerHTML;\r
402         }\r
403 \r
404 }\r