]> git.mxchange.org Git - friendica.git/blob - include/network.php
add the module to the page title
[friendica.git] / include / network.php
1 <?php
2
3
4 // curl wrapper. If binary flag is true, return binary
5 // results.
6
7 // Set the cookiejar argument to a string (e.g. "/tmp/friendica-cookies.txt")
8 // to preserve cookies from one request to the next.
9 if(! function_exists('fetch_url')) {
10 function fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accept_content=Null, $cookiejar = 0) {
11
12         $ret = z_fetch_url(
13                 $url,
14                 $binary,
15                 $redirects,
16                 array('timeout'=>$timeout,
17                 'accept_content'=>$accept_content,
18                 'cookiejar'=>$cookiejar
19                 ));
20
21         return($ret['body']);
22 }}
23
24 if(!function_exists('z_fetch_url')){
25 /**
26  * @brief fetches an URL.
27  *
28  * @param string $url
29  *    URL to fetch
30  * @param boolean $binary default false
31  *    TRUE if asked to return binary results (file download)
32  * @param int $redirects default 0
33  *    internal use, recursion counter
34  * @param array $opts (optional parameters) assoziative array with:
35  *  * \b accept_content => supply Accept: header with 'accept_content' as the value
36  *  * \b timeout => int seconds, default system config value or 60 seconds
37  *  * \b http_auth => username:password
38  *  * \b novalidate => do not validate SSL certs, default is to validate using our CA list
39  *  * \b nobody => only return the header
40  *      * \b cookiejar => path to cookie jar file
41  *
42  * @return array an assoziative array with:
43  *  * \e int \b return_code => HTTP return code or 0 if timeout or failure
44  *  * \e boolean \b success => boolean true (if HTTP 2xx result) or false
45  *  * \e string \b header => HTTP headers
46  *  * \e string \b body => fetched content
47  */
48 function z_fetch_url($url,$binary = false, &$redirects = 0, $opts=array()) {
49
50         $ret = array('return_code' => 0, 'success' => false, 'header' => "", 'body' => "");
51
52
53         $stamp1 = microtime(true);
54
55         $a = get_app();
56
57         $ch = @curl_init($url);
58         if(($redirects > 8) || (! $ch))
59                 return false;
60
61         @curl_setopt($ch, CURLOPT_HEADER, true);
62
63         if(x($opts,"cookiejar")) {
64                 curl_setopt($ch, CURLOPT_COOKIEJAR, $opts["cookiejar"]);
65                 curl_setopt($ch, CURLOPT_COOKIEFILE, $opts["cookiejar"]);
66         }
67
68 //  These settings aren't needed. We're following the location already.
69 //      @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
70 //      @curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
71
72         if (x($opts,'accept_content')){
73                 curl_setopt($ch,CURLOPT_HTTPHEADER, array (
74                         "Accept: " . $opts['accept_content']
75                 ));
76         }
77
78         @curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
79         @curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
80
81
82
83         if(x($opts,'headers')){
84                 @curl_setopt($ch, CURLOPT_HTTPHEADER, $opts['headers']);
85         }
86         if(x($opts,'nobody')){
87                 @curl_setopt($ch, CURLOPT_NOBODY, $opts['nobody']);
88         }
89         if(x($opts,'timeout')){
90                 @curl_setopt($ch, CURLOPT_TIMEOUT, $opts['timeout']);
91         } else {
92                 $curl_time = intval(get_config('system','curl_timeout'));
93                 @curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
94         }
95         // by default we will allow self-signed certs
96         // but you can override this
97
98         $check_cert = get_config('system','verifyssl');
99         @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
100         @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, (($check_cert) ? 2 : false));
101
102         $prx = get_config('system','proxy');
103         if(strlen($prx)) {
104                 @curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
105                 @curl_setopt($ch, CURLOPT_PROXY, $prx);
106                 $prxusr = @get_config('system','proxyuser');
107                 if(strlen($prxusr))
108                         @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
109         }
110         if($binary)
111                 @curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
112
113         $a->set_curl_code(0);
114
115         // don't let curl abort the entire application
116         // if it throws any errors.
117
118         $s = @curl_exec($ch);
119
120         $base = $s;
121         $curl_info = @curl_getinfo($ch);
122
123         $http_code = $curl_info['http_code'];
124         logger('fetch_url '.$url.': '.$http_code." ".$s, LOGGER_DATA);
125         $header = '';
126
127         // Pull out multiple headers, e.g. proxy and continuation headers
128         // allow for HTTP/2.x without fixing code
129
130         while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
131                 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
132                 $header .= $chunk;
133                 $base = substr($base,strlen($chunk));
134         }
135
136         if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
137                 $new_location_info = @parse_url($curl_info["redirect_url"]);
138                 $old_location_info = @parse_url($curl_info["url"]);
139
140                 $newurl = $curl_info["redirect_url"];
141
142                 if (($new_location_info["path"] == "") AND ($new_location_info["host"] != ""))
143                         $newurl = $new_location_info["scheme"]."://".$new_location_info["host"].$old_location_info["path"];
144
145                 $matches = array();
146                 if (preg_match('/(Location:|URI:)(.*?)\n/i', $header, $matches)) {
147                         $newurl = trim(array_pop($matches));
148                 }
149                 if(strpos($newurl,'/') === 0)
150                         $newurl = $old_location_info["scheme"]."://".$old_location_info["host"].$newurl;
151                 if (filter_var($newurl, FILTER_VALIDATE_URL)) {
152                         $redirects++;
153                         @curl_close($ch);
154                         return z_fetch_url($newurl,$binary, $redirects, $opts);
155                 }
156         }
157
158
159         $a->set_curl_code($http_code);
160         $a->set_curl_content_type($curl_info['content_type']);
161
162         $body = substr($s,strlen($header));
163         $a->set_curl_headers($header);
164
165
166
167         $rc = intval($http_code);
168         $ret['return_code'] = $rc;
169         $ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false);
170         if(! $ret['success']) {
171                 $ret['error'] = curl_error($ch);
172                 $ret['debug'] = $curl_info;
173                 logger('z_fetch_url: error: ' . $url . ': ' . $ret['error'], LOGGER_DEBUG);
174                 logger('z_fetch_url: debug: ' . print_r($curl_info,true), LOGGER_DATA);
175         }
176         $ret['body'] = substr($s,strlen($header));
177         $ret['header'] = $header;
178         if(x($opts,'debug')) {
179                 $ret['debug'] = $curl_info;
180         }
181         @curl_close($ch);
182
183         $a->save_timestamp($stamp1, "network");
184
185         return($ret);
186
187 }}
188
189 // post request to $url. $params is an array of post variables.
190
191 if(! function_exists('post_url')) {
192 function post_url($url,$params, $headers = null, &$redirects = 0, $timeout = 0) {
193         $stamp1 = microtime(true);
194
195         $a = get_app();
196         $ch = curl_init($url);
197         if(($redirects > 8) || (! $ch))
198                 return false;
199
200         logger("post_url: start ".$url, LOGGER_DATA);
201
202         curl_setopt($ch, CURLOPT_HEADER, true);
203         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
204         curl_setopt($ch, CURLOPT_POST,1);
205         curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
206         curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
207
208         if(intval($timeout)) {
209                 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
210         }
211         else {
212                 $curl_time = intval(get_config('system','curl_timeout'));
213                 curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
214         }
215
216         if(defined('LIGHTTPD')) {
217                 if(!is_array($headers)) {
218                         $headers = array('Expect:');
219                 } else {
220                         if(!in_array('Expect:', $headers)) {
221                                 array_push($headers, 'Expect:');
222                         }
223                 }
224         }
225         if($headers)
226                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
227
228         $check_cert = get_config('system','verifyssl');
229         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
230         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, (($check_cert) ? 2 : false));
231         $prx = get_config('system','proxy');
232         if(strlen($prx)) {
233                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
234                 curl_setopt($ch, CURLOPT_PROXY, $prx);
235                 $prxusr = get_config('system','proxyuser');
236                 if(strlen($prxusr))
237                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
238         }
239
240         $a->set_curl_code(0);
241
242         // don't let curl abort the entire application
243         // if it throws any errors.
244
245         $s = @curl_exec($ch);
246
247         $base = $s;
248         $curl_info = curl_getinfo($ch);
249         $http_code = $curl_info['http_code'];
250
251         logger("post_url: result ".$http_code." - ".$url, LOGGER_DATA);
252
253         $header = '';
254
255         // Pull out multiple headers, e.g. proxy and continuation headers
256         // allow for HTTP/2.x without fixing code
257
258         while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
259                 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
260                 $header .= $chunk;
261                 $base = substr($base,strlen($chunk));
262         }
263
264         if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
265                 $matches = array();
266                 preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
267                 $newurl = trim(array_pop($matches));
268                 if(strpos($newurl,'/') === 0)
269                         $newurl = $old_location_info["scheme"] . "://" . $old_location_info["host"] . $newurl;
270                 if (filter_var($newurl, FILTER_VALIDATE_URL)) {
271                         $redirects++;
272                         logger("post_url: redirect ".$url." to ".$newurl);
273                         return post_url($newurl,$params, $headers, $redirects, $timeout);
274                         //return fetch_url($newurl,false,$redirects,$timeout);
275                 }
276         }
277         $a->set_curl_code($http_code);
278         $body = substr($s,strlen($header));
279
280         $a->set_curl_headers($header);
281
282         curl_close($ch);
283
284         $a->save_timestamp($stamp1, "network");
285
286         logger("post_url: end ".$url, LOGGER_DATA);
287
288         return($body);
289 }}
290
291 // Generic XML return
292 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable
293 // of $st and an optional text <message> of $message and terminates the current process.
294
295 if(! function_exists('xml_status')) {
296 function xml_status($st, $message = '') {
297
298         $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
299
300         if($st)
301                 logger('xml_status returning non_zero: ' . $st . " message=" . $message);
302
303         header( "Content-type: text/xml" );
304         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
305         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
306         killme();
307 }}
308
309
310 if(! function_exists('http_status_exit')) {
311 function http_status_exit($val, $description = array()) {
312     $err = '';
313         if($val >= 400) {
314                 $err = 'Error';
315                 if (!isset($description["title"]))
316                         $description["title"] = $err." ".$val;
317         }
318         if($val >= 200 && $val < 300)
319                 $err = 'OK';
320
321         logger('http_status_exit ' . $val);
322         header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err);
323
324         if (isset($description["title"])) {
325                 $tpl = get_markup_template('http_status.tpl');
326                 echo replace_macros($tpl, array('$title' => $description["title"],
327                                                 '$description' => $description["description"]));
328         }
329
330         killme();
331
332 }}
333
334
335 // convert an XML document to a normalised, case-corrected array
336 // used by webfinger
337
338 if(! function_exists('convert_xml_element_to_array')) {
339 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
340
341         // If we're getting too deep, bail out
342         if ($recursion_depth > 512) {
343                 return(null);
344         }
345
346         if (!is_string($xml_element) &&
347         !is_array($xml_element) &&
348         (get_class($xml_element) == 'SimpleXMLElement')) {
349                 $xml_element_copy = $xml_element;
350                 $xml_element = get_object_vars($xml_element);
351         }
352
353         if (is_array($xml_element)) {
354                 $result_array = array();
355                 if (count($xml_element) <= 0) {
356                         return (trim(strval($xml_element_copy)));
357                 }
358
359                 foreach($xml_element as $key=>$value) {
360
361                         $recursion_depth++;
362                         $result_array[strtolower($key)] =
363                 convert_xml_element_to_array($value, $recursion_depth);
364                         $recursion_depth--;
365                 }
366                 if ($recursion_depth == 0) {
367                         $temp_array = $result_array;
368                         $result_array = array(
369                                 strtolower($xml_element_copy->getName()) => $temp_array,
370                         );
371                 }
372
373                 return ($result_array);
374
375         } else {
376                 return (trim(strval($xml_element)));
377         }
378 }}
379
380 // Given an email style address, perform webfinger lookup and
381 // return the resulting DFRN profile URL, or if no DFRN profile URL
382 // is located, returns an OStatus subscription template (prefixed
383 // with the string 'stat:' to identify it as on OStatus template).
384 // If this isn't an email style address just return $s.
385 // Return an empty string if email-style addresses but webfinger fails,
386 // or if the resultant personal XRD doesn't contain a supported
387 // subscription/friend-request attribute.
388
389 // amended 7/9/2011 to return an hcard which could save potentially loading
390 // a lengthy content page to scrape dfrn attributes
391
392 if(! function_exists('webfinger_dfrn')) {
393 function webfinger_dfrn($s,&$hcard) {
394         if(! strstr($s,'@')) {
395                 return $s;
396         }
397         $profile_link = '';
398
399         $links = webfinger($s);
400         logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
401         if(count($links)) {
402                 foreach($links as $link) {
403                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
404                                 $profile_link = $link['@attributes']['href'];
405                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
406                                 $profile_link = 'stat:' . $link['@attributes']['template'];
407                         if($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard')
408                                 $hcard = $link['@attributes']['href'];
409                 }
410         }
411         return $profile_link;
412 }}
413
414 // Given an email style address, perform webfinger lookup and
415 // return the array of link attributes from the personal XRD file.
416 // On error/failure return an empty array.
417
418
419 if(! function_exists('webfinger')) {
420 function webfinger($s, $debug = false) {
421         $host = '';
422         if(strstr($s,'@')) {
423                 $host = substr($s,strpos($s,'@') + 1);
424         }
425         if(strlen($host)) {
426                 $tpl = fetch_lrdd_template($host);
427                 logger('webfinger: lrdd template: ' . $tpl);
428                 if(strlen($tpl)) {
429                         $pxrd = str_replace('{uri}', urlencode('acct:' . $s), $tpl);
430                         logger('webfinger: pxrd: ' . $pxrd);
431                         $links = fetch_xrd_links($pxrd);
432                         if(! count($links)) {
433                                 // try with double slashes
434                                 $pxrd = str_replace('{uri}', urlencode('acct://' . $s), $tpl);
435                                 logger('webfinger: pxrd: ' . $pxrd);
436                                 $links = fetch_xrd_links($pxrd);
437                         }
438                         return $links;
439                 }
440         }
441         return array();
442 }}
443
444 if(! function_exists('lrdd')) {
445 function lrdd($uri, $debug = false) {
446
447         $a = get_app();
448
449         // default priority is host priority, host-meta first
450
451         $priority = 'host';
452
453         // All we have is an email address. Resource-priority is irrelevant
454         // because our URI isn't directly resolvable.
455
456         if(strstr($uri,'@')) {
457                 return(webfinger($uri));
458         }
459
460         // get the host meta file
461
462         $host = @parse_url($uri);
463
464         if($host) {
465                 $url  = ((x($host,'scheme')) ? $host['scheme'] : 'http') . '://';
466                 $url .= $host['host'] . '/.well-known/host-meta' ;
467         }
468         else
469                 return array();
470
471         logger('lrdd: constructed url: ' . $url);
472
473         $xml = fetch_url($url);
474
475         $headers = $a->get_curl_headers();
476
477         if (! $xml)
478                 return array();
479
480         logger('lrdd: host_meta: ' . $xml, LOGGER_DATA);
481
482         if(! stristr($xml,'<xrd'))
483                 return array();
484
485         $h = parse_xml_string($xml);
486         if(! $h)
487                 return array();
488
489         $arr = convert_xml_element_to_array($h);
490
491         if(isset($arr['xrd']['property'])) {
492                 $property = $arr['crd']['property'];
493                 if(! isset($property[0]))
494                         $properties = array($property);
495                 else
496                         $properties = $property;
497                 foreach($properties as $prop)
498                         if((string) $prop['@attributes'] === 'http://lrdd.net/priority/resource')
499                                 $priority = 'resource';
500         }
501
502         // save the links in case we need them
503
504         $links = array();
505
506         if(isset($arr['xrd']['link'])) {
507                 $link = $arr['xrd']['link'];
508                 if(! isset($link[0]))
509                         $links = array($link);
510                 else
511                         $links = $link;
512         }
513
514         // do we have a template or href?
515
516         if(count($links)) {
517                 foreach($links as $link) {
518                         if($link['@attributes']['rel'] && attribute_contains($link['@attributes']['rel'],'lrdd')) {
519                                 if(x($link['@attributes'],'template'))
520                                         $tpl = $link['@attributes']['template'];
521                                 elseif(x($link['@attributes'],'href'))
522                                         $href = $link['@attributes']['href'];
523                         }
524                 }
525         }
526
527         if((! isset($tpl)) || (! strpos($tpl,'{uri}')))
528                 $tpl = '';
529
530         if($priority === 'host') {
531                 if(strlen($tpl))
532                         $pxrd = str_replace('{uri}', urlencode($uri), $tpl);
533                 elseif(isset($href))
534                         $pxrd = $href;
535                 if(isset($pxrd)) {
536                         logger('lrdd: (host priority) pxrd: ' . $pxrd);
537                         $links = fetch_xrd_links($pxrd);
538                         return $links;
539                 }
540
541                 $lines = explode("\n",$headers);
542                 if(count($lines)) {
543                         foreach($lines as $line) {
544                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
545                                         return(fetch_xrd_links($matches[1]));
546                                         break;
547                                 }
548                         }
549                 }
550         }
551
552
553         // priority 'resource'
554
555
556         $html = fetch_url($uri);
557         $headers = $a->get_curl_headers();
558         logger('lrdd: headers=' . $headers, LOGGER_DEBUG);
559
560         // don't try and parse raw xml as html
561         if(! strstr($html,'<?xml')) {
562                 require_once('library/HTML5/Parser.php');
563
564                 try {
565                         $dom = HTML5_Parser::parse($html);
566                 } catch (DOMException $e) {
567                         logger('lrdd: parse error: ' . $e);
568                 }
569
570                 if(isset($dom) && $dom) {
571                         $items = $dom->getElementsByTagName('link');
572                         foreach($items as $item) {
573                                 $x = $item->getAttribute('rel');
574                                 if($x == "lrdd") {
575                                         $pagelink = $item->getAttribute('href');
576                                         break;
577                                 }
578                         }
579                 }
580         }
581
582         if(isset($pagelink))
583                 return(fetch_xrd_links($pagelink));
584
585         // next look in HTTP headers
586
587         $lines = explode("\n",$headers);
588         if(count($lines)) {
589                 foreach($lines as $line) {
590                         // TODO alter the following regex to support multiple relations (space separated)
591                         if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
592                                 $pagelink = $matches[1];
593                                 break;
594                         }
595                         // don't try and run feeds through the html5 parser
596                         if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml'))))
597                                 return array();
598                         if(stristr($html,'<rss') || stristr($html,'<feed'))
599                                 return array();
600                 }
601         }
602
603         if(isset($pagelink))
604                 return(fetch_xrd_links($pagelink));
605
606         // If we haven't found any links, return the host xrd links (which we have already fetched)
607
608         if(isset($links))
609                 return $links;
610
611         return array();
612
613 }}
614
615
616
617 // Given a host name, locate the LRDD template from that
618 // host. Returns the LRDD template or an empty string on
619 // error/failure.
620
621 if(! function_exists('fetch_lrdd_template')) {
622 function fetch_lrdd_template($host) {
623         $tpl = '';
624
625         $url1 = 'https://' . $host . '/.well-known/host-meta' ;
626         $url2 = 'http://' . $host . '/.well-known/host-meta' ;
627         $links = fetch_xrd_links($url1);
628         logger('fetch_lrdd_template from: ' . $url1);
629         logger('template (https): ' . print_r($links,true));
630         if(! count($links)) {
631                 logger('fetch_lrdd_template from: ' . $url2);
632                 $links = fetch_xrd_links($url2);
633                 logger('template (http): ' . print_r($links,true));
634         }
635         if(count($links)) {
636                 foreach($links as $link)
637                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd' && (!$link['@attributes']['type'] || $link['@attributes']['type'] === 'application/xrd+xml'))
638                                 $tpl = $link['@attributes']['template'];
639         }
640         if(! strpos($tpl,'{uri}'))
641                 $tpl = '';
642         return $tpl;
643 }}
644
645 // Given a URL, retrieve the page as an XRD document.
646 // Return an array of links.
647 // on error/failure return empty array.
648
649 if(! function_exists('fetch_xrd_links')) {
650 function fetch_xrd_links($url) {
651
652         $xrd_timeout = intval(get_config('system','xrd_timeout'));
653         $redirects = 0;
654         $xml = fetch_url($url,false,$redirects,(($xrd_timeout) ? $xrd_timeout : 20), "application/xrd+xml");
655
656         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
657
658         if ((! $xml) || (! stristr($xml,'<xrd')))
659                 return array();
660
661         // fix diaspora's bad xml
662         $xml = str_replace(array('href=&quot;','&quot;/>'),array('href="','"/>'),$xml);
663
664         $h = parse_xml_string($xml);
665         if(! $h)
666                 return array();
667
668         $arr = convert_xml_element_to_array($h);
669
670         $links = array();
671
672         if(isset($arr['xrd']['link'])) {
673                 $link = $arr['xrd']['link'];
674                 if(! isset($link[0]))
675                         $links = array($link);
676                 else
677                         $links = $link;
678         }
679         if(isset($arr['xrd']['alias'])) {
680                 $alias = $arr['xrd']['alias'];
681                 if(! isset($alias[0]))
682                         $aliases = array($alias);
683                 else
684                         $aliases = $alias;
685                 if(is_array($aliases) && count($aliases)) {
686                         foreach($aliases as $alias) {
687                                 $links[]['@attributes'] = array('rel' => 'alias' , 'href' => $alias);
688                         }
689                 }
690         }
691
692         logger('fetch_xrd_links: ' . print_r($links,true), LOGGER_DATA);
693
694         return $links;
695
696 }}
697
698
699 // Take a URL from the wild, prepend http:// if necessary
700 // and check DNS to see if it's real (or check if is a valid IP address)
701 // return true if it's OK, false if something is wrong with it
702
703 if(! function_exists('validate_url')) {
704 function validate_url(&$url) {
705
706         if(get_config('system','disable_url_validation'))
707                 return true;
708         // no naked subdomains (allow localhost for tests)
709         if(strpos($url,'.') === false && strpos($url,'/localhost/') === false)
710                 return false;
711         if(substr($url,0,4) != 'http')
712                 $url = 'http://' . $url;
713         $h = @parse_url($url);
714
715         if(($h) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR) || filter_var($h['host'], FILTER_VALIDATE_IP) )) {
716                 return true;
717         }
718         return false;
719 }}
720
721 // checks that email is an actual resolvable internet address
722
723 if(! function_exists('validate_email')) {
724 function validate_email($addr) {
725
726         if(get_config('system','disable_email_validation'))
727                 return true;
728
729         if(! strpos($addr,'@'))
730                 return false;
731         $h = substr($addr,strpos($addr,'@') + 1);
732
733         if(($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX) || filter_var($h, FILTER_VALIDATE_IP) )) {
734                 return true;
735         }
736         return false;
737 }}
738
739 // Check $url against our list of allowed sites,
740 // wildcards allowed. If allowed_sites is unset return true;
741 // If url is allowed, return true.
742 // otherwise, return false
743
744 if(! function_exists('allowed_url')) {
745 function allowed_url($url) {
746
747         $h = @parse_url($url);
748
749         if(! $h) {
750                 return false;
751         }
752
753         $str_allowed = get_config('system','allowed_sites');
754         if(! $str_allowed)
755                 return true;
756
757         $found = false;
758
759         $host = strtolower($h['host']);
760
761         // always allow our own site
762
763         if($host == strtolower($_SERVER['SERVER_NAME']))
764                 return true;
765
766         $fnmatch = function_exists('fnmatch');
767         $allowed = explode(',',$str_allowed);
768
769         if(count($allowed)) {
770                 foreach($allowed as $a) {
771                         $pat = strtolower(trim($a));
772                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
773                                 $found = true;
774                                 break;
775                         }
776                 }
777         }
778         return $found;
779 }}
780
781 // check if email address is allowed to register here.
782 // Compare against our list (wildcards allowed).
783 // Returns false if not allowed, true if allowed or if
784 // allowed list is not configured.
785
786 if(! function_exists('allowed_email')) {
787 function allowed_email($email) {
788
789
790         $domain = strtolower(substr($email,strpos($email,'@') + 1));
791         if(! $domain)
792                 return false;
793
794         $str_allowed = get_config('system','allowed_email');
795         if(! $str_allowed)
796                 return true;
797
798         $found = false;
799
800         $fnmatch = function_exists('fnmatch');
801         $allowed = explode(',',$str_allowed);
802
803         if(count($allowed)) {
804                 foreach($allowed as $a) {
805                         $pat = strtolower(trim($a));
806                         if(($fnmatch && fnmatch($pat,$domain)) || ($pat == $domain)) {
807                                 $found = true;
808                                 break;
809                         }
810                 }
811         }
812         return $found;
813 }}
814
815
816 if(! function_exists('avatar_img')) {
817 function avatar_img($email) {
818
819         $a = get_app();
820
821         $avatar['size'] = 175;
822         $avatar['email'] = $email;
823         $avatar['url'] = '';
824         $avatar['success'] = false;
825
826         call_hooks('avatar_lookup', $avatar);
827
828         if(! $avatar['success'])
829                 $avatar['url'] = $a->get_baseurl() . '/images/person-175.jpg';
830
831         logger('Avatar: ' . $avatar['email'] . ' ' . $avatar['url'], LOGGER_DEBUG);
832         return $avatar['url'];
833 }}
834
835
836 if(! function_exists('parse_xml_string')) {
837 function parse_xml_string($s,$strict = true) {
838         if($strict) {
839                 if(! strstr($s,'<?xml'))
840                         return false;
841                 $s2 = substr($s,strpos($s,'<?xml'));
842         }
843         else
844                 $s2 = $s;
845         libxml_use_internal_errors(true);
846
847         $x = @simplexml_load_string($s2);
848         if(! $x) {
849                 logger('libxml: parse: error: ' . $s2, LOGGER_DATA);
850                 foreach(libxml_get_errors() as $err)
851                         logger('libxml: parse: ' . $err->code." at ".$err->line.":".$err->column." : ".$err->message, LOGGER_DATA);
852                 libxml_clear_errors();
853         }
854         return $x;
855 }}
856
857 function add_fcontact($arr,$update = false) {
858
859         if($update) {
860                 $r = q("UPDATE `fcontact` SET
861                         `name` = '%s',
862                         `photo` = '%s',
863                         `request` = '%s',
864                         `nick` = '%s',
865                         `addr` = '%s',
866                         `batch` = '%s',
867                         `notify` = '%s',
868                         `poll` = '%s',
869                         `confirm` = '%s',
870                         `alias` = '%s',
871                         `pubkey` = '%s',
872                         `updated` = '%s'
873                         WHERE `url` = '%s' AND `network` = '%s'",
874                         dbesc($arr['name']),
875                         dbesc($arr['photo']),
876                         dbesc($arr['request']),
877                         dbesc($arr['nick']),
878                         dbesc($arr['addr']),
879                         dbesc($arr['batch']),
880                         dbesc($arr['notify']),
881                         dbesc($arr['poll']),
882                         dbesc($arr['confirm']),
883                         dbesc($arr['alias']),
884                         dbesc($arr['pubkey']),
885                         dbesc(datetime_convert()),
886                         dbesc($arr['url']),
887                         dbesc($arr['network'])
888                 );
889         }
890         else {
891                 $r = q("insert into fcontact ( `url`,`name`,`photo`,`request`,`nick`,`addr`,
892                         `batch`, `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated` )
893                         values('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')",
894                         dbesc($arr['url']),
895                         dbesc($arr['name']),
896                         dbesc($arr['photo']),
897                         dbesc($arr['request']),
898                         dbesc($arr['nick']),
899                         dbesc($arr['addr']),
900                         dbesc($arr['batch']),
901                         dbesc($arr['notify']),
902                         dbesc($arr['poll']),
903                         dbesc($arr['confirm']),
904                         dbesc($arr['network']),
905                         dbesc($arr['alias']),
906                         dbesc($arr['pubkey']),
907                         dbesc(datetime_convert())
908                 );
909         }
910
911         return $r;
912 }
913
914
915 function scale_external_images($srctext, $include_link = true, $scale_replace = false) {
916
917         // Suppress "view full size"
918         if (intval(get_config('system','no_view_full_size')))
919                 $include_link = false;
920
921         $a = get_app();
922
923         // Picture addresses can contain special characters
924         $s = htmlspecialchars_decode($srctext);
925
926         $matches = null;
927         $c = preg_match_all('/\[img.*?\](.*?)\[\/img\]/ism',$s,$matches,PREG_SET_ORDER);
928         if($c) {
929                 require_once('include/Photo.php');
930                 foreach($matches as $mtch) {
931                         logger('scale_external_image: ' . $mtch[1]);
932
933                         $hostname = str_replace('www.','',substr($a->get_baseurl(),strpos($a->get_baseurl(),'://')+3));
934                         if(stristr($mtch[1],$hostname))
935                                 continue;
936
937                         // $scale_replace, if passed, is an array of two elements. The
938                         // first is the name of the full-size image. The second is the
939                         // name of a remote, scaled-down version of the full size image.
940                         // This allows Friendica to display the smaller remote image if
941                         // one exists, while still linking to the full-size image
942                         if($scale_replace)
943                                 $scaled = str_replace($scale_replace[0], $scale_replace[1], $mtch[1]);
944                         else
945                                 $scaled = $mtch[1];
946                         $i = @fetch_url($scaled);
947                         if(! $i)
948                                 return $srctext;
949
950                         // guess mimetype from headers or filename
951                         $type = guess_image_type($mtch[1],true);
952
953                         if($i) {
954                                 $ph = new Photo($i, $type);
955                                 if($ph->is_valid()) {
956                                         $orig_width = $ph->getWidth();
957                                         $orig_height = $ph->getHeight();
958
959                                         if($orig_width > 640 || $orig_height > 640) {
960
961                                                 $ph->scaleImage(640);
962                                                 $new_width = $ph->getWidth();
963                                                 $new_height = $ph->getHeight();
964                                                 logger('scale_external_images: ' . $orig_width . '->' . $new_width . 'w ' . $orig_height . '->' . $new_height . 'h' . ' match: ' . $mtch[0], LOGGER_DEBUG);
965                                                 $s = str_replace($mtch[0],'[img=' . $new_width . 'x' . $new_height. ']' . $scaled . '[/img]'
966                                                         . "\n" . (($include_link)
967                                                                 ? '[url=' . $mtch[1] . ']' . t('view full size') . '[/url]' . "\n"
968                                                                 : ''),$s);
969                                                 logger('scale_external_images: new string: ' . $s, LOGGER_DEBUG);
970                                         }
971                                 }
972                         }
973                 }
974         }
975
976         // replace the special char encoding
977         $s = htmlspecialchars($s,ENT_NOQUOTES,'UTF-8');
978         return $s;
979 }
980
981
982 function fix_contact_ssl_policy(&$contact,$new_policy) {
983
984         $ssl_changed = false;
985         if((intval($new_policy) == SSL_POLICY_SELFSIGN || $new_policy === 'self') && strstr($contact['url'],'https:')) {
986                 $ssl_changed = true;
987                 $contact['url']     =   str_replace('https:','http:',$contact['url']);
988                 $contact['request'] =   str_replace('https:','http:',$contact['request']);
989                 $contact['notify']  =   str_replace('https:','http:',$contact['notify']);
990                 $contact['poll']    =   str_replace('https:','http:',$contact['poll']);
991                 $contact['confirm'] =   str_replace('https:','http:',$contact['confirm']);
992                 $contact['poco']    =   str_replace('https:','http:',$contact['poco']);
993         }
994
995         if((intval($new_policy) == SSL_POLICY_FULL || $new_policy === 'full') && strstr($contact['url'],'http:')) {
996                 $ssl_changed = true;
997                 $contact['url']     =   str_replace('http:','https:',$contact['url']);
998                 $contact['request'] =   str_replace('http:','https:',$contact['request']);
999                 $contact['notify']  =   str_replace('http:','https:',$contact['notify']);
1000                 $contact['poll']    =   str_replace('http:','https:',$contact['poll']);
1001                 $contact['confirm'] =   str_replace('http:','https:',$contact['confirm']);
1002                 $contact['poco']    =   str_replace('http:','https:',$contact['poco']);
1003         }
1004
1005         if($ssl_changed) {
1006                 q("update contact set
1007                         url = '%s',
1008                         request = '%s',
1009                         notify = '%s',
1010                         poll = '%s',
1011                         confirm = '%s',
1012                         poco = '%s'
1013                         where id = %d limit 1",
1014                         dbesc($contact['url']),
1015                         dbesc($contact['request']),
1016                         dbesc($contact['notify']),
1017                         dbesc($contact['poll']),
1018                         dbesc($contact['confirm']),
1019                         dbesc($contact['poco']),
1020                         intval($contact['id'])
1021                 );
1022         }
1023 }
1024
1025
1026
1027 /**
1028  * xml2array() will convert the given XML text to an array in the XML structure.
1029  * Link: http://www.bin-co.com/php/scripts/xml2array/
1030  * Portions significantly re-written by mike@macgirvin.com for Friendica (namespaces, lowercase tags, get_attribute default changed, more...)
1031  * Arguments : $contents - The XML text
1032  *                $namespaces - true or false include namespace information in the returned array as array elements.
1033  *                $get_attributes - 1 or 0. If this is 1 the function will get the attributes as well as the tag values - this results in a different array structure in the return value.
1034  *                $priority - Can be 'tag' or 'attribute'. This will change the way the resulting array sturcture. For 'tag', the tags are given more importance.
1035  * Return: The parsed XML in an array form. Use print_r() to see the resulting array structure.
1036  * Examples: $array =  xml2array(file_get_contents('feed.xml'));
1037  *              $array =  xml2array(file_get_contents('feed.xml', true, 1, 'attribute'));
1038  */
1039
1040 function xml2array($contents, $namespaces = true, $get_attributes=1, $priority = 'attribute') {
1041     if(!$contents) return array();
1042
1043     if(!function_exists('xml_parser_create')) {
1044         logger('xml2array: parser function missing');
1045         return array();
1046     }
1047
1048
1049         libxml_use_internal_errors(true);
1050         libxml_clear_errors();
1051
1052         if($namespaces)
1053             $parser = @xml_parser_create_ns("UTF-8",':');
1054         else
1055             $parser = @xml_parser_create();
1056
1057         if(! $parser) {
1058                 logger('xml2array: xml_parser_create: no resource');
1059                 return array();
1060         }
1061
1062     xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, "UTF-8");
1063         // http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
1064     xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
1065     xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
1066     @xml_parse_into_struct($parser, trim($contents), $xml_values);
1067     @xml_parser_free($parser);
1068
1069     if(! $xml_values) {
1070                 logger('xml2array: libxml: parse error: ' . $contents, LOGGER_DATA);
1071                 foreach(libxml_get_errors() as $err)
1072                         logger('libxml: parse: ' . $err->code . " at " . $err->line . ":" . $err->column . " : " . $err->message, LOGGER_DATA);
1073                 libxml_clear_errors();
1074                 return;
1075         }
1076
1077     //Initializations
1078     $xml_array = array();
1079     $parents = array();
1080     $opened_tags = array();
1081     $arr = array();
1082
1083     $current = &$xml_array; // Reference
1084
1085     // Go through the tags.
1086     $repeated_tag_index = array(); // Multiple tags with same name will be turned into an array
1087     foreach($xml_values as $data) {
1088         unset($attributes,$value); // Remove existing values, or there will be trouble
1089
1090         // This command will extract these variables into the foreach scope
1091         // tag(string), type(string), level(int), attributes(array).
1092         extract($data); // We could use the array by itself, but this cooler.
1093
1094         $result = array();
1095         $attributes_data = array();
1096
1097         if(isset($value)) {
1098             if($priority == 'tag') $result = $value;
1099             else $result['value'] = $value; // Put the value in a assoc array if we are in the 'Attribute' mode
1100         }
1101
1102         //Set the attributes too.
1103         if(isset($attributes) and $get_attributes) {
1104             foreach($attributes as $attr => $val) {
1105                 if($priority == 'tag') $attributes_data[$attr] = $val;
1106                 else $result['@attributes'][$attr] = $val; // Set all the attributes in a array called 'attr'
1107             }
1108         }
1109
1110         // See tag status and do the needed.
1111                 if($namespaces && strpos($tag,':')) {
1112                         $namespc = substr($tag,0,strrpos($tag,':'));
1113                         $tag = strtolower(substr($tag,strlen($namespc)+1));
1114                         $result['@namespace'] = $namespc;
1115                 }
1116                 $tag = strtolower($tag);
1117
1118                 if($type == "open") {   // The starting of the tag '<tag>'
1119             $parent[$level-1] = &$current;
1120             if(!is_array($current) or (!in_array($tag, array_keys($current)))) { // Insert New tag
1121                 $current[$tag] = $result;
1122                 if($attributes_data) $current[$tag. '_attr'] = $attributes_data;
1123                 $repeated_tag_index[$tag.'_'.$level] = 1;
1124
1125                 $current = &$current[$tag];
1126
1127             } else { // There was another element with the same tag name
1128
1129                 if(isset($current[$tag][0])) { // If there is a 0th element it is already an array
1130                     $current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;
1131                     $repeated_tag_index[$tag.'_'.$level]++;
1132                 } else { // This section will make the value an array if multiple tags with the same name appear together
1133                     $current[$tag] = array($current[$tag],$result); // This will combine the existing item and the new item together to make an array
1134                     $repeated_tag_index[$tag.'_'.$level] = 2;
1135
1136                     if(isset($current[$tag.'_attr'])) { // The attribute of the last(0th) tag must be moved as well
1137                         $current[$tag]['0_attr'] = $current[$tag.'_attr'];
1138                         unset($current[$tag.'_attr']);
1139                     }
1140
1141                 }
1142                 $last_item_index = $repeated_tag_index[$tag.'_'.$level]-1;
1143                 $current = &$current[$tag][$last_item_index];
1144             }
1145
1146         } elseif($type == "complete") { // Tags that ends in 1 line '<tag />'
1147             //See if the key is already taken.
1148             if(!isset($current[$tag])) { //New Key
1149                 $current[$tag] = $result;
1150                 $repeated_tag_index[$tag.'_'.$level] = 1;
1151                 if($priority == 'tag' and $attributes_data) $current[$tag. '_attr'] = $attributes_data;
1152
1153             } else { // If taken, put all things inside a list(array)
1154                 if(isset($current[$tag][0]) and is_array($current[$tag])) { // If it is already an array...
1155
1156                     // ...push the new element into that array.
1157                     $current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;
1158
1159                     if($priority == 'tag' and $get_attributes and $attributes_data) {
1160                         $current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
1161                     }
1162                     $repeated_tag_index[$tag.'_'.$level]++;
1163
1164                 } else { // If it is not an array...
1165                     $current[$tag] = array($current[$tag],$result); //...Make it an array using using the existing value and the new value
1166                     $repeated_tag_index[$tag.'_'.$level] = 1;
1167                     if($priority == 'tag' and $get_attributes) {
1168                         if(isset($current[$tag.'_attr'])) { // The attribute of the last(0th) tag must be moved as well
1169
1170                             $current[$tag]['0_attr'] = $current[$tag.'_attr'];
1171                             unset($current[$tag.'_attr']);
1172                         }
1173
1174                         if($attributes_data) {
1175                             $current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
1176                         }
1177                     }
1178                     $repeated_tag_index[$tag.'_'.$level]++; // 0 and 1 indexes are already taken
1179                 }
1180             }
1181
1182         } elseif($type == 'close') { // End of tag '</tag>'
1183             $current = &$parent[$level-1];
1184         }
1185     }
1186
1187     return($xml_array);
1188 }
1189
1190 function original_url($url, $depth=1, $fetchbody = false) {
1191
1192         $a = get_app();
1193
1194         // Remove Analytics Data from Google and other tracking platforms
1195         $urldata = parse_url($url);
1196         if (is_string($urldata["query"])) {
1197                 $query = $urldata["query"];
1198                 parse_str($query, $querydata);
1199
1200                 if (is_array($querydata))
1201                         foreach ($querydata AS $param=>$value)
1202                                 if (in_array($param, array("utm_source", "utm_medium", "utm_term", "utm_content", "utm_campaign",
1203                                                         "wt_mc", "pk_campaign", "pk_kwd", "mc_cid", "mc_eid",
1204                                                         "fb_action_ids", "fb_action_types", "fb_ref",
1205                                                         "awesm", "wtrid",
1206                                                         "woo_campaign", "woo_source", "woo_medium", "woo_content", "woo_term"))) {
1207
1208                                         $pair = $param."=".urlencode($value);
1209                                         $url = str_replace($pair, "", $url);
1210
1211                                         // Second try: if the url isn't encoded completely
1212                                         $pair = $param."=".str_replace(" ", "+", $value);
1213                                         $url = str_replace($pair, "", $url);
1214
1215                                         // Third try: Maybey the url isn't encoded at all
1216                                         $pair = $param."=".$value;
1217                                         $url = str_replace($pair, "", $url);
1218
1219                                         $url = str_replace(array("?&", "&&"), array("?", ""), $url);
1220                                 }
1221
1222                 if (substr($url, -1, 1) == "?")
1223                         $url = substr($url, 0, -1);
1224         }
1225
1226         if ($depth > 10)
1227                 return($url);
1228
1229         $url = trim($url, "'");
1230
1231         $stamp1 = microtime(true);
1232
1233         $siteinfo = array();
1234         $ch = curl_init();
1235         curl_setopt($ch, CURLOPT_URL, $url);
1236         curl_setopt($ch, CURLOPT_HEADER, 1);
1237         curl_setopt($ch, CURLOPT_NOBODY, 1);
1238         curl_setopt($ch, CURLOPT_TIMEOUT, 10);
1239         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1240         curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
1241
1242         $header = curl_exec($ch);
1243         $curl_info = @curl_getinfo($ch);
1244         $http_code = $curl_info['http_code'];
1245         curl_close($ch);
1246
1247         $a->save_timestamp($stamp1, "network");
1248
1249         if ((($curl_info['http_code'] == "301") OR ($curl_info['http_code'] == "302"))
1250                 AND (($curl_info['redirect_url'] != "") OR ($curl_info['location'] != ""))) {
1251                 if ($curl_info['redirect_url'] != "")
1252                         return(original_url($curl_info['redirect_url'], ++$depth, $fetchbody));
1253                 else
1254                         return(original_url($curl_info['location'], ++$depth, $fetchbody));
1255         }
1256
1257         // Check for redirects in the meta elements of the body if there are no redirects in the header.
1258         if (!$fetchbody)
1259                 return(original_url($url, ++$depth, true));
1260
1261         // if the file is too large then exit
1262         if ($curl_info["download_content_length"] > 1000000)
1263                 return($url);
1264
1265         // if it isn't a HTML file then exit
1266         if (($curl_info["content_type"] != "") AND !strstr(strtolower($curl_info["content_type"]),"html"))
1267                 return($url);
1268
1269         $stamp1 = microtime(true);
1270
1271         $ch = curl_init();
1272         curl_setopt($ch, CURLOPT_URL, $url);
1273         curl_setopt($ch, CURLOPT_HEADER, 0);
1274         curl_setopt($ch, CURLOPT_NOBODY, 0);
1275         curl_setopt($ch, CURLOPT_TIMEOUT, 10);
1276         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1277         curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
1278
1279         $body = curl_exec($ch);
1280         curl_close($ch);
1281
1282         $a->save_timestamp($stamp1, "network");
1283
1284         if (trim($body) == "")
1285                 return($url);
1286
1287         // Check for redirect in meta elements
1288         $doc = new DOMDocument();
1289         @$doc->loadHTML($body);
1290
1291         $xpath = new DomXPath($doc);
1292
1293         $list = $xpath->query("//meta[@content]");
1294         foreach ($list as $node) {
1295                 $attr = array();
1296                 if ($node->attributes->length)
1297                         foreach ($node->attributes as $attribute)
1298                                 $attr[$attribute->name] = $attribute->value;
1299
1300                 if (@$attr["http-equiv"] == 'refresh') {
1301                         $path = $attr["content"];
1302                         $pathinfo = explode(";", $path);
1303                         $content = "";
1304                         foreach ($pathinfo AS $value)
1305                                 if (substr(strtolower($value), 0, 4) == "url=")
1306                                         return(original_url(substr($value, 4), ++$depth));
1307                 }
1308         }
1309
1310         return($url);
1311 }
1312
1313 if (!function_exists('short_link')) {
1314 function short_link($url) {
1315         require_once('library/slinky.php');
1316         $slinky = new Slinky($url);
1317         $yourls_url = get_config('yourls','url1');
1318         if ($yourls_url) {
1319                 $yourls_username = get_config('yourls','username1');
1320                 $yourls_password = get_config('yourls', 'password1');
1321                 $yourls_ssl = get_config('yourls', 'ssl1');
1322                 $yourls = new Slinky_YourLS();
1323                 $yourls->set('username', $yourls_username);
1324                 $yourls->set('password', $yourls_password);
1325                 $yourls->set('ssl', $yourls_ssl);
1326                 $yourls->set('yourls-url', $yourls_url);
1327                 $slinky->set_cascade( array($yourls, new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL()));
1328         } else {
1329                 // setup a cascade of shortening services
1330                 // try to get a short link from these services
1331                 // in the order ur1.ca, trim, id.gd, tinyurl
1332                 $slinky->set_cascade(array(new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL()));
1333         }
1334         return $slinky->short();
1335 }};