]> git.mxchange.org Git - friendica.git/blob - src/Content/OEmbed.php
db2c130eaf4d93303f6847b9ecdf222eda90237b
[friendica.git] / src / Content / OEmbed.php
1 <?php\r
2 \r
3 /*\r
4  * To change this license header, choose License Headers in Project Properties.\r
5  * To change this template file, choose Tools | Templates\r
6  * and open the template in the editor.\r
7  */\r
8 \r
9 namespace Friendica\Content;\r
10 \r
11 use Friendica\Core\Cache;\r
12 use Friendica\Core\System;\r
13 use Friendica\ParseUrl;\r
14 use Friendica\Core\Config;\r
15 use Friendica\Database\DBM;\r
16 use dba;\r
17 use DOMDocument;\r
18 use DOMXPath;\r
19 use DOMNode;\r
20 \r
21 require_once 'include/dba.php';\r
22 require_once 'mod/proxy.php';\r
23 \r
24 /**\r
25  * Description of OEmbed\r
26  *\r
27  * @author benlo\r
28  */\r
29 class OEmbed\r
30 {\r
31         public static function replaceCallback($matches)\r
32         {\r
33                 $embedurl = $matches[1];\r
34                 $j = OEmbed::fetchURL($embedurl);\r
35                 $s = OEmbed::formatObject($j);\r
36 \r
37                 return $s;\r
38         }\r
39 \r
40         /**\r
41          * @brief Get data from an URL to embed its content.\r
42          *\r
43          * @param string $embedurl The URL from which the data should be fetched.\r
44          * @param bool $no_rich_type If set to true rich type content won't be fetched.\r
45          *\r
46          * @return bool|object Returns object with embed content or false if no embedable\r
47          *       content exists\r
48          */\r
49         public static function fetchURL($embedurl, $no_rich_type = false)\r
50         {\r
51                 $embedurl = trim($embedurl, "'");\r
52                 $embedurl = trim($embedurl, '"');\r
53 \r
54                 $a = get_app();\r
55 \r
56                 $condition = array('url' => normalise_link($embedurl));\r
57                 $r = dba::select('oembed', array('content'), $condition, array('limit' => 1));\r
58 \r
59                 if (DBM::is_result($r)) {\r
60                         $txt = $r["content"];\r
61                 } else {\r
62                         $txt = Cache::get($a->videowidth . $embedurl);\r
63                 }\r
64                 // These media files should now be caught in bbcode.php\r
65                 // left here as a fallback in case this is called from another source\r
66 \r
67                 $noexts = array("mp3", "mp4", "ogg", "ogv", "oga", "ogm", "webm");\r
68                 $ext = pathinfo(strtolower($embedurl), PATHINFO_EXTENSION);\r
69 \r
70 \r
71                 if (is_null($txt)) {\r
72                         $txt = "";\r
73 \r
74                         if (!in_array($ext, $noexts)) {\r
75                                 // try oembed autodiscovery\r
76                                 $redirects = 0;\r
77                                 $html_text = fetch_url($embedurl, false, $redirects, 15, "text/*");\r
78                                 if ($html_text) {\r
79                                         $dom = @DOMDocument::loadHTML($html_text);\r
80                                         if ($dom) {\r
81                                                 $xpath = new DOMXPath($dom);\r
82                                                 $entries = $xpath->query("//link[@type='application/json+oembed']");\r
83                                                 foreach ($entries as $e) {\r
84                                                         $href = $e->getAttributeNode("href")->nodeValue;\r
85                                                         $txt = fetch_url($href . '&maxwidth=' . $a->videowidth);\r
86                                                         break;\r
87                                                 }\r
88                                                 $entries = $xpath->query("//link[@type='text/json+oembed']");\r
89                                                 foreach ($entries as $e) {\r
90                                                         $href = $e->getAttributeNode("href")->nodeValue;\r
91                                                         $txt = fetch_url($href . '&maxwidth=' . $a->videowidth);\r
92                                                         break;\r
93                                                 }\r
94                                         }\r
95                                 }\r
96                         }\r
97 \r
98                         $txt = trim($txt);\r
99 \r
100                         if ($txt[0] != "{") {\r
101                                 $txt = '{"type":"error"}';\r
102                         } else { //save in cache\r
103                                 $j = json_decode($txt);\r
104                                 if ($j->type != "error") {\r
105                                         dba::insert('oembed', array('url' => normalise_link($embedurl),\r
106                                                 'content' => $txt, 'created' => datetime_convert()), true);\r
107                                 }\r
108 \r
109                                 Cache::set($a->videowidth . $embedurl, $txt, CACHE_DAY);\r
110                         }\r
111                 }\r
112 \r
113                 $j = json_decode($txt);\r
114 \r
115                 if (!is_object($j)) {\r
116                         return false;\r
117                 }\r
118 \r
119                 // Always embed the SSL version\r
120                 if (isset($j->html)) {\r
121                         $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
122                 }\r
123 \r
124                 $j->embedurl = $embedurl;\r
125 \r
126                 // If fetching information doesn't work, then improve via internal functions\r
127                 if (($j->type == "error") || ($no_rich_type && ($j->type == "rich"))) {\r
128                         $data = ParseUrl::getSiteinfoCached($embedurl, true, false);\r
129                         $j->type = $data["type"];\r
130 \r
131                         if ($j->type == "photo") {\r
132                                 $j->url = $data["url"];\r
133                                 //$j->width = $data["images"][0]["width"];\r
134                                 //$j->height = $data["images"][0]["height"];\r
135                         }\r
136 \r
137                         if (isset($data["title"])) {\r
138                                 $j->title = $data["title"];\r
139                         }\r
140 \r
141                         if (isset($data["text"])) {\r
142                                 $j->description = $data["text"];\r
143                         }\r
144 \r
145                         if (is_array($data["images"])) {\r
146                                 $j->thumbnail_url = $data["images"][0]["src"];\r
147                                 $j->thumbnail_width = $data["images"][0]["width"];\r
148                                 $j->thumbnail_height = $data["images"][0]["height"];\r
149                         }\r
150                 }\r
151 \r
152                 call_hooks('oembed_fetch_url', $embedurl, $j);\r
153 \r
154                 return $j;\r
155         }\r
156 \r
157         public static function formatObject($j)\r
158         {\r
159                 $embedurl = $j->embedurl;\r
160                 $jhtml = OEmbed::iframe($j->embedurl, (isset($j->width) ? $j->width : null), (isset($j->height) ? $j->height : null));\r
161                 $ret = "<span class='oembed " . $j->type . "'>";\r
162                 switch ($j->type) {\r
163                         case "video":\r
164                                 if (isset($j->thumbnail_url)) {\r
165                                         $tw = (isset($j->thumbnail_width) && intval($j->thumbnail_width)) ? $j->thumbnail_width : 200;\r
166                                         $th = (isset($j->thumbnail_height) && intval($j->thumbnail_height)) ? $j->thumbnail_height : 180;\r
167                                         // make sure we don't attempt divide by zero, fallback is a 1:1 ratio\r
168                                         $tr = (($th) ? $tw / $th : 1);\r
169 \r
170                                         $th = 120;\r
171                                         $tw = $th * $tr;\r
172                                         $tpl = get_markup_template('oembed_video.tpl');\r
173                                         $ret.=replace_macros($tpl, array(\r
174                                                 '$baseurl' => System::baseUrl(),\r
175                                                 '$embedurl' => $embedurl,\r
176                                                 '$escapedhtml' => base64_encode($jhtml),\r
177                                                 '$tw' => $tw,\r
178                                                 '$th' => $th,\r
179                                                 '$turl' => $j->thumbnail_url,\r
180                                         ));\r
181                                 } else {\r
182                                         $ret = $jhtml;\r
183                                 }\r
184                                 //$ret.="<br>";\r
185                                 break;\r
186                         case "photo":\r
187                                 $ret.= "<img width='" . $j->width . "' src='" . proxy_url($j->url) . "'>";\r
188                                 break;\r
189                         case "link":\r
190                                 break;\r
191                         case "rich":\r
192                                 // not so safe..\r
193                                 if (!Config::get("system", "no_oembed_rich_content")) {\r
194                                         $ret.= proxy_parse_html($jhtml);\r
195                                 }\r
196                                 break;\r
197                 }\r
198 \r
199                 // add link to source if not present in "rich" type\r
200                 if ($j->type != 'rich' || !strpos($j->html, $embedurl)) {\r
201                         $ret .= "<h4>";\r
202                         if (isset($j->title)) {\r
203                                 if (isset($j->provider_name)) {\r
204                                         $ret .= $j->provider_name . ": ";\r
205                                 }\r
206 \r
207                                 $embedlink = (isset($j->title)) ? $j->title : $embedurl;\r
208                                 $ret .= "<a href='$embedurl' rel='oembed'>$embedlink</a>";\r
209                                 if (isset($j->author_name)) {\r
210                                         $ret.=" (" . $j->author_name . ")";\r
211                                 }\r
212                         } elseif (isset($j->provider_name) || isset($j->author_name)) {\r
213                                 $embedlink = "";\r
214                                 if (isset($j->provider_name)) {\r
215                                         $embedlink .= $j->provider_name;\r
216                                 }\r
217 \r
218                                 if (isset($j->author_name)) {\r
219                                         if ($embedlink != "") {\r
220                                                 $embedlink .= ": ";\r
221                                         }\r
222 \r
223                                         $embedlink .= $j->author_name;\r
224                                 }\r
225                                 if (trim($embedlink) == "") {\r
226                                         $embedlink = $embedurl;\r
227                                 }\r
228 \r
229                                 $ret .= "<a href='$embedurl' rel='oembed'>$embedlink</a>";\r
230                         }\r
231                         //if (isset($j->author_name)) $ret.=" by ".$j->author_name;\r
232                         //if (isset($j->provider_name)) $ret.=" on ".$j->provider_name;\r
233                         $ret .= "</h4>";\r
234                 } else {\r
235                         // add <a> for html2bbcode conversion\r
236                         $ret .= "<a href='$embedurl' rel='oembed'>$embedurl</a>";\r
237                 }\r
238                 $ret.="</span>";\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 = OEmbed::buildXPath("class", "oembed");\r
272                         $entries = $xpath->query("//span[$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 OEmbed::getInnerHTML($dom->getElementsByTagName("body")->item(0));\r
282                 } else {\r
283                         return $text;\r
284                 }\r
285         }\r
286 \r
287         /**\r
288          * @brief Generates the iframe HTML for an oembed attachment.\r
289          *\r
290          * Width and height are given by the remote, and are regularly too small for\r
291          * the generated iframe.\r
292          *\r
293          * The width is entirely discarded for the actual width of the post, while fixed\r
294          * height is used as a starting point before the inevitable resizing.\r
295          *\r
296          * Since the iframe is automatically resized on load, there are no need for ugly\r
297          * and impractical scrollbars.\r
298          *\r
299          * @param string $src Original remote URL to embed\r
300          * @param string $width\r
301          * @param string $height\r
302          * @return string formatted HTML\r
303          *\r
304          * @see oembed_format_object()\r
305          */\r
306         private static function iframe($src, $width, $height)\r
307         {\r
308                 $a = get_app();\r
309 \r
310                 if (!$height || strstr($height, '%')) {\r
311                         $height = '200';\r
312                 }\r
313                 $width = '100%';\r
314 \r
315                 $s = System::baseUrl() . '/oembed/' . base64url_encode($src);\r
316                 return '<iframe onload="resizeIframe(this);" class="embed_rich" height="' . $height . '" width="' . $width . '" src="' . $s . '" allowfullscreen scrolling="no" frameborder="no">' . t('Embedded content') . '</iframe>';\r
317         }\r
318 \r
319         /**\r
320          * Generates an XPath query to select elements whose provided attribute contains\r
321          * the provided value in a space-separated list.\r
322          *\r
323          * @brief Generates attribute search XPath string\r
324          *\r
325          * @param string $attr Name of the attribute to seach\r
326          * @param string $value Value to search in a space-separated list\r
327          * @return string\r
328          */\r
329         private static function buildXPath($attr, $value)\r
330         {\r
331                 // https://www.westhoffswelt.de/blog/2009/6/9/select-html-elements-with-more-than-one-css-class-using-xpath\r
332                 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
333         }\r
334 \r
335         /**\r
336          * Returns the inner XML string of a provided DOMNode\r
337          *\r
338          * @brief Returns the inner XML string of a provided DOMNode\r
339          *\r
340          * @param DOMNode $node\r
341          * @return string\r
342          */\r
343         private static function getInnerHTML(DOMNode $node)\r
344         {\r
345                 $innerHTML = '';\r
346                 $children = $node->childNodes;\r
347                 foreach ($children as $child) {\r
348                         $innerHTML .= $child->ownerDocument->saveXML($child);\r
349                 }\r
350                 return $innerHTML;\r
351         }\r
352 }\r