]> git.mxchange.org Git - friendica.git/blob - boot.php
send unfollow before removing ostatus contact
[friendica.git] / boot.php
1 <?php
2
3 set_time_limit(0);
4
5 define ( 'BUILD_ID',               1019   );
6 define ( 'DFRN_PROTOCOL_VERSION',  '2.0'  );
7
8 define ( 'EOL',                    "<br />\r\n"     );
9 define ( 'ATOM_TIME',              'Y-m-d\TH:i:s\Z' );
10
11 // log levels
12
13 define ( 'LOGGER_NORMAL',          0 );
14 define ( 'LOGGER_TRACE',           1 );
15 define ( 'LOGGER_DEBUG',           2 );
16 define ( 'LOGGER_DATA',            3 );
17 define ( 'LOGGER_ALL',             4 );
18
19 // registration policy
20
21 define ( 'REGISTER_CLOSED',        0 );
22 define ( 'REGISTER_APPROVE',       1 );
23 define ( 'REGISTER_OPEN',          2 );
24
25 // relationship types
26
27 define ( 'REL_VIP',        1);
28 define ( 'REL_FAN',        2);
29 define ( 'REL_BUD',        3);
30
31
32 // page/profile types
33 // PAGE_NORMAL is a typical personal profile account
34 // PAGE_SOAPBOX automatically approves all friend requests as REL_FAN, (readonly)
35 // PAGE_COMMUNITY automatically approves all friend requests as REL_FAN, but with 
36 // write access to wall and comments (no email and not included in page owner's ACL lists)
37 // PAGE_FREELOVE automatically approves all friend requests as full friends (REL_BUD). 
38
39 define ( 'PAGE_NORMAL',            0 );
40 define ( 'PAGE_SOAPBOX',           1 );
41 define ( 'PAGE_COMMUNITY',         2 );
42 define ( 'PAGE_FREELOVE',          3 );
43
44 // Maximum number of "people who like (or don't like) this" 
45 // that we will list by name
46
47 define ( 'MAX_LIKERS',    75);
48
49 // email notification options
50
51 define ( 'NOTIFY_INTRO',   0x0001 );
52 define ( 'NOTIFY_CONFIRM', 0x0002 );
53 define ( 'NOTIFY_WALL',    0x0004 );
54 define ( 'NOTIFY_COMMENT', 0x0008 );
55 define ( 'NOTIFY_MAIL',    0x0010 );
56
57 // various namespaces we may need to parse
58
59 define ( 'NAMESPACE_DFRN' ,           'http://purl.org/macgirvin/dfrn/1.0' ); 
60 define ( 'NAMESPACE_THREAD' ,         'http://purl.org/syndication/thread/1.0' );
61 define ( 'NAMESPACE_TOMB' ,           'http://purl.org/atompub/tombstones/1.0' );
62 define ( 'NAMESPACE_ACTIVITY',        'http://activitystrea.ms/spec/1.0/' );
63 define ( 'NAMESPACE_ACTIVITY_SCHEMA', 'http://activitystrea.ms/schema/1.0/' );
64 define ( 'NAMESPACE_MEDIA',           'http://purl.org/syndication/atommedia' );
65 define ( 'NAMESPACE_SALMON_ME',       'http://salmon-protocol.org/ns/magic-env' );
66 define ( 'NAMESPACE_OSTATUSSUB',      'http://ostatus.org/schema/1.0/subscribe' );
67 define ( 'NAMESPACE_GEORSS',          'http://www.georss.org/georss' );
68 define ( 'NAMESPACE_POCO',            'http://portablecontacts.net/spec/1.0' );
69 define ( 'NAMESPACE_FEED',            'http://schemas.google.com/g/2010#updates-from' );
70
71 // activity stream defines
72
73 define ( 'ACTIVITY_LIKE',        NAMESPACE_ACTIVITY_SCHEMA . 'like' );
74 define ( 'ACTIVITY_DISLIKE',     NAMESPACE_DFRN            . '/dislike' );
75 define ( 'ACTIVITY_OBJ_HEART',   NAMESPACE_DFRN            . '/heart' );
76
77 define ( 'ACTIVITY_FRIEND',      NAMESPACE_ACTIVITY_SCHEMA . 'make-friend' );
78 define ( 'ACTIVITY_FOLLOW',      NAMESPACE_ACTIVITY_SCHEMA . 'follow' );
79 define ( 'ACTIVITY_UNFOLLOW',    NAMESPACE_ACTIVITY_SCHEMA . 'unfollow' );
80 define ( 'ACTIVITY_POST',        NAMESPACE_ACTIVITY_SCHEMA . 'post' );
81 define ( 'ACTIVITY_UPDATE',      NAMESPACE_ACTIVITY_SCHEMA . 'update' );
82 define ( 'ACTIVITY_TAG',         NAMESPACE_ACTIVITY_SCHEMA . 'tag' );
83
84 define ( 'ACTIVITY_OBJ_COMMENT', NAMESPACE_ACTIVITY_SCHEMA . 'comment' );
85 define ( 'ACTIVITY_OBJ_NOTE',    NAMESPACE_ACTIVITY_SCHEMA . 'note' );
86 define ( 'ACTIVITY_OBJ_PERSON',  NAMESPACE_ACTIVITY_SCHEMA . 'person' );
87 define ( 'ACTIVITY_OBJ_PHOTO',   NAMESPACE_ACTIVITY_SCHEMA . 'photo' );
88 define ( 'ACTIVITY_OBJ_P_PHOTO', NAMESPACE_ACTIVITY_SCHEMA . 'profile-photo' );
89 define ( 'ACTIVITY_OBJ_ALBUM',   NAMESPACE_ACTIVITY_SCHEMA . 'photo-album' );
90
91 // item weight for query ordering
92
93 define ( 'GRAVITY_PARENT',       0);
94 define ( 'GRAVITY_LIKE',         3);
95 define ( 'GRAVITY_COMMENT',      6);
96
97
98 // Our main application structure for the life of this page
99 // Primarily deals with the URL that got us here
100 // and tries to make some sense of it, and 
101 // stores our page contents and config storage
102 // and anything else that might need to be passed around 
103 // before we spit the page out. 
104
105 if(! class_exists('App')) {
106 class App {
107
108         public  $module_loaded = false;
109         public  $config;
110         public  $page;
111         public  $profile;
112         public  $user;
113         public  $cid;
114         public  $contact;
115         public  $content;
116         public  $data;
117         public  $error = false;
118         public  $cmd;
119         public  $argv;
120         public  $argc;
121         public  $module;
122         public  $pager;
123         public  $strings;   
124         public  $path;
125         public  $interactive = true;
126
127         private $scheme;
128         private $hostname;
129         private $baseurl;
130         private $db;
131
132         private $curl_code;
133         private $curl_headers;
134
135         function __construct() {
136
137                 $this->config = array();
138                 $this->page = array();
139                 $this->pager= array();
140
141                 $this->scheme = ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']))      ?  'https' : 'http' );
142
143                 if(x($_SERVER,'SERVER_NAME'))
144                         $this->hostname = $_SERVER['SERVER_NAME'];
145
146                 set_include_path("include/$this->hostname" . PATH_SEPARATOR . 'include' . PATH_SEPARATOR . '.' );
147
148                 if((x($_SERVER,'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'],0,2) === "q=")
149                         $_SERVER['QUERY_STRING'] = substr($_SERVER['QUERY_STRING'],2);
150                 if(x($_GET,'q'))
151                         $this->cmd = trim($_GET['q'],'/');
152
153
154                 $this->argv = explode('/',$this->cmd);
155                 $this->argc = count($this->argv);
156                 if((array_key_exists('0',$this->argv)) && strlen($this->argv[0])) {
157                         $this->module = $this->argv[0];
158                 }
159                 else {
160                         $this->module = 'home';
161                 }
162
163                 if($this->cmd === '.well-known/host-meta')
164                         require_once('include/hostxrd.php');
165
166
167                 $this->pager['page'] = ((x($_GET,'page')) ? $_GET['page'] : 1);
168                 $this->pager['itemspage'] = 50;
169                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
170                 $this->pager['total'] = 0;
171         }
172
173         function get_baseurl($ssl = false) {
174                 if(strlen($this->baseurl))
175                         return $this->baseurl;
176
177                 $this->baseurl = (($ssl) ? 'https' : $this->scheme) . "://" . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );
178                 return $this->baseurl;
179         }
180
181         function set_baseurl($url) {
182                 $this->baseurl = $url;
183                 $this->hostname = basename($url);
184         }
185
186         function get_hostname() {
187                 return $this->hostname;
188         }
189
190         function set_hostname($h) {
191                 $this->hostname = $h;
192         }
193
194         function set_path($p) {
195                 $this->path = trim(trim($p),'/');
196         } 
197
198         function get_path() {
199                 return $this->path;
200         }
201
202         function set_pager_total($n) {
203                 $this->pager['total'] = intval($n);
204         }
205
206         function set_pager_itemspage($n) {
207                 $this->pager['itemspage'] = intval($n);
208                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
209
210         } 
211
212         function init_pagehead() {
213                 $tpl = load_view_file("view/head.tpl");
214                 $this->page['htmlhead'] = replace_macros($tpl,array(
215                         '$baseurl' => $this->get_baseurl()
216                 ));
217         }
218
219         function set_curl_code($code) {
220                 $this->curl_code = $code;
221         }
222
223         function get_curl_code() {
224                 return $this->curl_code;
225         }
226
227         function set_curl_headers($headers) {
228                 $this->curl_headers = $headers;
229         }
230
231         function get_curl_headers() {
232                 return $this->curl_headers;
233         }
234
235
236 }}
237
238 // retrieve the App structure
239 // useful in functions which require it but don't get it passed to them
240
241 if(! function_exists('get_app')) {
242 function get_app() {
243         global $a;
244         return $a;
245 }};
246
247
248 // Multi-purpose function to check variable state.
249 // Usage: x($var) or $x($array,'key')
250 // returns false if variable/key is not set
251 // if variable is set, returns 1 if has 'non-zero' value, otherwise returns 0.
252 // e.g. x('') or x(0) returns 0;
253
254 if(! function_exists('x')) {
255 function x($s,$k = NULL) {
256         if($k != NULL) {
257                 if((is_array($s)) && (array_key_exists($k,$s))) {
258                         if($s[$k])
259                                 return (int) 1;
260                         return (int) 0;
261                 }
262                 return false;
263         }
264         else {          
265                 if(isset($s)) {
266                         if($s) {
267                                 return (int) 1;
268                         }
269                         return (int) 0;
270                 }
271                 return false;
272         }
273 }}
274
275 // called from db initialisation if db is dead.
276
277 if(! function_exists('system_unavailable')) {
278 function system_unavailable() {
279         include('system_unavailable.php');
280         killme();
281 }}
282
283 // Primarily involved with database upgrade, but also sets the 
284 // base url for use in cmdline programs which don't have
285 // $_SERVER variables.
286
287 if(! function_exists('check_config')) {
288 function check_config(&$a) {
289
290         load_config('system');
291
292         $build = get_config('system','build');
293         if(! x($build))
294                 $build = set_config('system','build',BUILD_ID);
295
296         $url = get_config('system','url');
297         if(! x($url))
298                 $url = set_config('system','url',$a->get_baseurl());
299
300         if($build != BUILD_ID) {
301                 $stored = intval($build);
302                 $current = intval(BUILD_ID);
303                 if(($stored < $current) && file_exists('update.php')) {
304                         // We're reporting a different version than what is currently installed.
305                         // Run any existing update scripts to bring the database up to current.
306
307                         require_once('update.php');
308                         for($x = $stored; $x < $current; $x ++) {
309                                 if(function_exists('update_' . $x)) {
310                                         $func = 'update_' . $x;
311                                         $func($a);
312                                 }
313                         }
314                         set_config('system','build', BUILD_ID);
315                 }
316         }
317         return;
318 }}
319
320
321 // This is our template processor.
322 // $s is the string requiring macro substitution.
323 // $r is an array of key value pairs (search => replace)
324 // returns substituted string.
325 // WARNING: this is pretty basic, and doesn't properly handle search strings that are substrings of each other.
326 // For instance if 'test' => "foo" and 'testing' => "bar", testing could become either bar or fooing, 
327 // depending on the order in which they were declared in the array.   
328
329 if(! function_exists('replace_macros')) {  
330 function replace_macros($s,$r) {
331
332         $search = array();
333         $replace = array();
334
335         if(is_array($r) && count($r)) {
336                 foreach ($r as $k => $v ) {
337                         $search[] =  $k;
338                         $replace[] = $v;
339                 }
340         }
341         return str_replace($search,$replace,$s);
342 }}
343
344
345 // load string translation table for alternate language
346
347 if(! function_exists('load_translation_table')) {
348 function load_translation_table($lang) {
349         global $a;
350
351         if(file_exists("view/$lang/strings.php"))
352                 include("view/$lang/strings.php");
353 }}
354
355 // translate string if translation exists
356
357 if(! function_exists('t')) {
358 function t($s) {
359
360         $a = get_app();
361
362         if(x($a->strings,$s))
363                 return $a->strings[$s];
364         return $s;
365 }}
366
367 // curl wrapper. If binary flag is true, return binary
368 // results. 
369
370 if(! function_exists('fetch_url')) {
371 function fetch_url($url,$binary = false, &$redirects = 0) {
372         $ch = curl_init($url);
373         if(($redirects > 8) || (! $ch)) 
374                 return false;
375
376         curl_setopt($ch, CURLOPT_HEADER, true);
377         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
378
379
380         $curl_time = intval(get_config('system','curl_timeout'));
381         curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
382
383         // by default we will allow self-signed certs
384         // but you can override this
385
386         $check_cert = get_config('system','verifyssl');
387         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
388
389         $prx = get_config('system','proxy');
390         if(strlen($prx)) {
391                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
392                 curl_setopt($ch, CURLOPT_PROXY, $prx);
393                 $prxusr = get_config('system','proxyuser');
394                 if(strlen($prxusr))
395                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
396         }
397         if($binary)
398                 curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
399
400         $s = curl_exec($ch);
401
402         $http_code = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
403         $header = substr($s,0,strpos($s,"\r\n\r\n"));
404         if(stristr($header,'100') && (strlen($header) < 30)) {
405                 // 100 Continue has two headers, get the real one
406                 $s = substr($s,strlen($header)+4);
407                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
408         }
409         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
410         $matches = array();
411         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
412         $url = trim(array_pop($matches));
413         $url_parsed = parse_url($url);
414         if (isset($url_parsed)) {
415             $redirects++;
416             return fetch_url($url,$binary,$redirects);
417         }
418     }
419         $a = get_app();
420         $a->set_curl_code($http_code);
421         $body = substr($s,strlen($header)+4);
422         $a->set_curl_headers($header);
423
424         curl_close($ch);
425         return($body);
426 }}
427
428 // post request to $url. $params is an array of post variables.
429
430 if(! function_exists('post_url')) {
431 function post_url($url,$params, $headers = null, &$redirects = 0) {
432         $ch = curl_init($url);
433         if(($redirects > 8) || (! $ch)) 
434                 return false;
435
436         curl_setopt($ch, CURLOPT_HEADER, true);
437         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
438         curl_setopt($ch, CURLOPT_POST,1);
439         curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
440
441         $curl_time = intval(get_config('system','curl_timeout'));
442         curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
443
444         if(is_array($headers))
445                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
446
447         $check_cert = get_config('system','verifyssl');
448         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
449         $prx = get_config('system','proxy');
450         if(strlen($prx)) {
451                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
452                 curl_setopt($ch, CURLOPT_PROXY, $prx);
453                 $prxusr = get_config('system','proxyuser');
454                 if(strlen($prxusr))
455                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
456         }
457
458         $s = curl_exec($ch);
459
460         $http_code = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
461         $header = substr($s,0,strpos($s,"\r\n\r\n"));
462         if(stristr($header,'100') && (strlen($header) < 30)) {
463                 // 100 Continue has two headers, get the real one
464                 $s = substr($s,strlen($header)+4);
465                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
466         }
467         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
468         $matches = array();
469         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
470         $url = trim(array_pop($matches));
471         $url_parsed = parse_url($url);
472         if (isset($url_parsed)) {
473             $redirects++;
474             return post_url($url,$binary,$headers,$redirects);
475         }
476     }
477         $a = get_app();
478         $a->set_curl_code($http_code);
479         $body = substr($s,strlen($header)+4);
480         $a->set_curl_headers($header);
481
482         curl_close($ch);
483         return($body);
484 }}
485
486 // random hash, 64 chars
487
488 if(! function_exists('random_string')) {
489 function random_string() {
490         return(hash('sha256',uniqid(rand(),true)));
491 }}
492
493 // This is our primary input filter. The high bit hack only involved some old
494 // IE browser, forget which. 
495 // Use this on any text input where angle chars are not valid or permitted
496 // They will be replaced with safer brackets. This may be filtered further
497 // if these are not allowed either.   
498
499 if(! function_exists('notags')) {
500 function notags($string) {
501         // protect against :<> with high-bit set
502         return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
503 }}
504
505 // use this on "body" or "content" input where angle chars shouldn't be removed,
506 // and allow them to be safely displayed.
507
508 if(! function_exists('escape_tags')) {
509 function escape_tags($string) {
510
511         return(htmlspecialchars($string));
512 }}
513
514 // wrapper for adding a login box. If $register == true provide a registration
515 // link. This will most always depend on the value of $a->config['register_policy'].
516 // returns the complete html for inserting into the page
517
518 if(! function_exists('login')) {
519 function login($register = false) {
520         $o = "";
521         $register_html = (($register) ? load_view_file("view/register-link.tpl") : "");
522
523
524         if(x($_SESSION,'authenticated')) {
525                 $o = load_view_file("view/logout.tpl");
526         }
527         else {
528                 $o = load_view_file("view/login.tpl");
529
530                 $o = replace_macros($o,array('$register_html' => $register_html ));
531         }
532         return $o;
533 }}
534
535 // generate a string that's random, but usually pronounceable. 
536 // used to generate initial passwords
537
538 if(! function_exists('autoname')) {
539 function autoname($len) {
540
541         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u'); 
542         if(mt_rand(0,5) == 4)
543                 $vowels[] = 'y';
544
545         $cons = array(
546                         'b','bl','br',
547                         'c','ch','cl','cr',
548                         'd','dr',
549                         'f','fl','fr',
550                         'g','gh','gl','gr',
551                         'h',
552                         'j',
553                         'k','kh','kl','kr',
554                         'l',
555                         'm',
556                         'n',
557                         'p','ph','pl','pr',
558                         'qu',
559                         'r','rh',
560                         's','sc','sh','sm','sp','st',
561                         't','th','tr',
562                         'v',
563                         'w','wh',
564                         'x',
565                         'z','zh'
566                         );
567
568         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
569                                 'nd','ng','nk','nt','rn','rp','rt');
570
571         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
572                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
573
574         $start = mt_rand(0,2);
575         if($start == 0)
576                 $table = $vowels;
577         else
578                 $table = $cons;
579
580         $word = '';
581
582         for ($x = 0; $x < $len; $x ++) {
583                 $r = mt_rand(0,count($table) - 1);
584                 $word .= $table[$r];
585   
586                 if($table == $vowels)
587                         $table = array_merge($cons,$midcons);
588                 else
589                         $table = $vowels;
590
591         }
592
593         $word = substr($word,0,$len);
594
595         foreach($noend as $noe) {
596                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
597                         $word = substr($word,0,-1);
598                         break;
599                 }
600         }
601         if(substr($word,-1) == 'q')
602                 $word = substr($word,0,-1);    
603         return $word;
604 }}
605
606 // Used to end the current process, after saving session state. 
607
608 if(! function_exists('killme')) {
609 function killme() {
610         session_write_close();
611         exit;
612 }}
613
614 // redirect to another URL and terminate this process.
615
616 if(! function_exists('goaway')) {
617 function goaway($s) {
618         header("Location: $s");
619         killme();
620 }}
621
622 // Generic XML return
623 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable 
624 // of $st and an optional text <message> of $message and terminates the current process. 
625
626 if(! function_exists('xml_status')) {
627 function xml_status($st, $message = '') {
628
629         $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
630
631         if($st)
632                 logger('xml_status returning non_zero: ' . $st . " message=" . $message);
633
634         header( "Content-type: text/xml" );
635         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
636         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
637         killme();
638 }}
639
640 // Returns the uid of locally logged in user or false.
641
642 if(! function_exists('local_user')) {
643 function local_user() {
644         if((x($_SESSION,'authenticated')) && (x($_SESSION,'uid')))
645                 return intval($_SESSION['uid']);
646         return false;
647 }}
648
649 // Returns contact id of authenticated site visitor or false
650
651 if(! function_exists('remote_user')) {
652 function remote_user() {
653         if((x($_SESSION,'authenticated')) && (x($_SESSION,'visitor_id')))
654                 return intval($_SESSION['visitor_id']);
655         return false;
656 }}
657
658 // contents of $s are displayed prominently on the page the next time
659 // a page is loaded. Usually used for errors or alerts.
660
661 if(! function_exists('notice')) {
662 function notice($s) {
663         $a = get_app();
664         if($a->interactive)
665                 $_SESSION['sysmsg'] .= $s;
666 }}
667
668 // wrapper around config to limit the text length of an incoming message
669
670 if(! function_exists('get_max_import_size')) {
671 function get_max_import_size() {
672         global $a;
673         return ((x($a->config,'max_import_size')) ? $a->config['max_import_size'] : 0 );
674 }}
675
676
677 // escape text ($str) for XML transport
678 // returns escaped text.
679
680 if(! function_exists('xmlify')) {
681 function xmlify($str) {
682         $buffer = '';
683         
684         for($x = 0; $x < strlen($str); $x ++) {
685                 $char = $str[$x];
686         
687                 switch( $char ) {
688
689                         case "\r" :
690                                 break;
691                         case "&" :
692                                 $buffer .= '&amp;';
693                                 break;
694                         case "'" :
695                                 $buffer .= '&apos;';
696                                 break;
697
698                         case "\"" :
699                                 $buffer .= '&quot;';
700                                 break;
701                         case '<' :
702                                 $buffer .= '&lt;';
703                                 break;
704                         case '>' :
705                                 $buffer .= '&gt;';
706                                 break;
707                         case "\n" :
708                                 $buffer .= "\n";
709                                 break;
710                         default :
711                                 $buffer .= $char;
712                                 break;
713                 }       
714         }
715         $buffer = trim($buffer);
716         return($buffer);
717 }}
718
719 // undo an xmlify
720 // pass xml escaped text ($s), returns unescaped text
721
722 if(! function_exists('unxmlify')) {
723 function unxmlify($s) {
724         $ret = str_replace('&amp;','&', $s);
725         $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
726         return $ret;    
727 }}
728
729 // convenience wrapper, reverse the operation "bin2hex"
730
731 if(! function_exists('hex2bin')) {
732 function hex2bin($s) {
733         return(pack("H*",$s));
734 }}
735
736 // Automatic pagination.
737 // To use, get the count of total items.
738 // Then call $a->set_pager_total($number_items);
739 // Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
740 // Then call paginate($a) after the end of the display loop to insert the pager block on the page
741 // (assuming there are enough items to paginate).
742 // When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
743 // will limit the results to the correct items for the current page. 
744 // The actual page handling is then accomplished at the application layer. 
745
746 if(! function_exists('paginate')) {
747 function paginate(&$a) {
748         $o = '';
749         $stripped = preg_replace('/(&page=[0-9]*)/','',$_SERVER['QUERY_STRING']);
750         $stripped = str_replace('q=','',$stripped);
751         $stripped = trim($stripped,'/');
752         $url = $a->get_baseurl() . '/' . $stripped;
753
754
755           if($a->pager['total'] > $a->pager['itemspage']) {
756                 $o .= '<div class="pager">';
757                 if($a->pager['page'] != 1)
758                         $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
759
760                 $o .=  "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
761
762                 $numpages = $a->pager['total'] / $a->pager['itemspage'];
763
764                 $numstart = 1;
765                 $numstop = $numpages;
766
767                 if($numpages > 14) {
768                         $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
769                         $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
770                 }
771    
772                 for($i = $numstart; $i <= $numstop; $i++){
773                         if($i == $a->pager['page'])
774                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
775                         else
776                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
777                         $o .= '</span> ';
778                 }
779
780                 if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
781                         if($i == $a->pager['page'])
782                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
783                         else
784                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
785                         $o .= '</span> ';
786                 }
787
788                 $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
789                 $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
790
791                 if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
792                         $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
793                 $o .= '</div>'."\r\n";
794         }
795         return $o;
796 }}
797
798 // Turn user/group ACLs stored as angle bracketed text into arrays
799
800 if(! function_exists('expand_acl')) {
801 function expand_acl($s) {
802         // turn string array of angle-bracketed elements into numeric array
803         // e.g. "<1><2><3>" => array(1,2,3);
804         $ret = array();
805
806         if(strlen($s)) {
807                 $t = str_replace('<','',$s);
808                 $a = explode('>',$t);
809                 foreach($a as $aa) {
810                         if(intval($aa))
811                                 $ret[] = intval($aa);
812                 }
813         }
814         return $ret;
815 }}              
816
817 // Used to wrap ACL elements in angle brackets for storage 
818
819 if(! function_exists('sanitise_acl')) {
820 function sanitise_acl(&$item) {
821         if(intval($item))
822                 $item = '<' . intval(notags(trim($item))) . '>';
823         else
824                 unset($item);
825 }}
826
827 // retrieve a "family" of config variables from database to cached storage
828
829 if(! function_exists('load_config')) {
830 function load_config($family) {
831         global $a;
832         $r = q("SELECT * FROM `config` WHERE `cat` = '%s'",
833                 dbesc($family)
834         );
835         if(count($r)) {
836                 foreach($r as $rr) {
837                         $k = $rr['k'];
838                         $a->config[$family][$k] = $rr['v'];
839                 }
840         }
841 }}
842
843 // get a particular config variable given the family name
844 // and key. Returns false if not set.
845 // $instore is only used by the set_config function
846 // to determine if the key already exists in the DB
847 // If a key is found in the DB but doesn't exist in
848 // local config cache, pull it into the cache so we don't have
849 // to hit the DB again for this item.
850
851 if(! function_exists('get_config')) {
852 function get_config($family, $key, $instore = false) {
853
854         global $a;
855
856         if(! $instore) {
857                 if(isset($a->config[$family][$key])) {
858                         if($a->config[$family][$key] === '!<unset>!') {
859                                 return false;
860                         }
861                         return $a->config[$family][$key];
862                 }
863         }
864         $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
865                 dbesc($family),
866                 dbesc($key)
867         );
868         if(count($ret)) {
869                 $a->config[$family][$key] = $ret[0]['v'];
870                 return $ret[0]['v'];
871         }
872         else {
873                 $a->config[$family][$key] = '!<unset>!';
874         }
875         return false;
876 }}
877
878 // Store a config value ($value) in the category ($family)
879 // under the key ($key)
880 // Return the value, or false if the database update failed
881
882 if(! function_exists('set_config')) {
883 function set_config($family,$key,$value) {
884
885         global $a;
886         $a->config[$family][$key] = $value;
887
888         if(get_config($family,$key,true) === false) {
889                 $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
890                         dbesc($family),
891                         dbesc($key),
892                         dbesc($value)
893                 );
894                 if($ret) 
895                         return $value;
896                 return $ret;
897         }
898         $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
899                 dbesc($value),
900                 dbesc($family),
901                 dbesc($key)
902         );
903         if($ret)
904                 return $value;
905         return $ret;
906 }}
907
908 // convert an XML document to a normalised, case-corrected array
909 // used by webfinger
910
911 if(! function_exists('convert_xml_element_to_array')) {
912 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
913
914         // If we're getting too deep, bail out
915         if ($recursion_depth > 512) {
916                 return(null);
917         }
918
919         if (!is_string($xml_element) &&
920         !is_array($xml_element) &&
921         (get_class($xml_element) == 'SimpleXMLElement')) {
922                 $xml_element_copy = $xml_element;
923                 $xml_element = get_object_vars($xml_element);
924         }
925
926         if (is_array($xml_element)) {
927                 $result_array = array();
928                 if (count($xml_element) <= 0) {
929                         return (trim(strval($xml_element_copy)));
930                 }
931
932                 foreach($xml_element as $key=>$value) {
933
934                         $recursion_depth++;
935                         $result_array[strtolower($key)] =
936                 convert_xml_element_to_array($value, $recursion_depth);
937                         $recursion_depth--;
938                 }
939                 if ($recursion_depth == 0) {
940                         $temp_array = $result_array;
941                         $result_array = array(
942                                 strtolower($xml_element_copy->getName()) => $temp_array,
943                         );
944                 }
945
946                 return ($result_array);
947
948         } else {
949                 return (trim(strval($xml_element)));
950         }
951 }}
952
953 // Given an email style address, perform webfinger lookup and 
954 // return the resulting DFRN profile URL, or if no DFRN profile URL
955 // is located, returns an OStatus subscription template (prefixed 
956 // with the string 'stat:' to identify it as on OStatus template).
957 // If this isn't an email style address just return $s.
958 // Return an empty string if email-style addresses but webfinger fails,
959 // or if the resultant personal XRD doesn't contain a supported 
960 // subscription/friend-request attribute.
961
962 if(! function_exists('webfinger_dfrn')) {
963 function webfinger_dfrn($s) {
964         if(! strstr($s,'@')) {
965                 return $s;
966         }
967         $links = webfinger($s);
968         logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
969         if(count($links)) {
970                 foreach($links as $link)
971                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
972                                 return $link['@attributes']['href'];
973                 foreach($links as $link)
974                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
975                                 return 'stat:' . $link['@attributes']['template'];              
976         }
977         return '';
978 }}
979
980 // Given an email style address, perform webfinger lookup and 
981 // return the array of link attributes from the personal XRD file.
982 // On error/failure return an empty array.
983
984
985 if(! function_exists('webfinger')) {
986 function webfinger($s) {
987         $host = '';
988         if(strstr($s,'@')) {
989                 $host = substr($s,strpos($s,'@') + 1);
990         }
991         if(strlen($host)) {
992                 $tpl = fetch_lrdd_template($host);
993                 logger('webfinger: lrdd template: ' . $tpl);
994                 if(strlen($tpl)) {
995                         $pxrd = str_replace('{uri}', urlencode('acct:'.$s), $tpl);
996                         $links = fetch_xrd_links($pxrd);
997                         if(! count($links)) {
998                                 // try with double slashes
999                                 $pxrd = str_replace('{uri}', urlencode('acct://'.$s), $tpl);
1000                                 $links = fetch_xrd_links($pxrd);
1001                         }
1002                         return $links;
1003                 }
1004         }
1005         return array();
1006 }}
1007
1008 if(! function_exists('lrdd')) {
1009 function lrdd($uri) {
1010
1011         $a = get_app();
1012
1013         if(strstr($uri,'@')) {  
1014                 return(webfinger($uri));
1015         }
1016         else {
1017                 $html = fetch_url($uri);
1018                 $headers = $a->get_curl_headers();
1019                 $lines = explode("\n",$headers);
1020                 if(count($lines)) {
1021                         foreach($lines as $line) {                              
1022                                 // TODO alter the following regex to support multiple relations (space separated)
1023                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1024                                         $link = $matches[1];
1025                                         break;
1026                                 }
1027                         }
1028                 }
1029                 if(! isset($link)) {
1030                         // parse the page of the supplied URL looking for rel links
1031
1032                         require_once('library/HTML5/Parser.php');
1033                         $dom = HTML5_Parser::parse($html);
1034
1035                         if($dom) {
1036                                 $items = $dom->getElementsByTagName('link');
1037
1038                                 foreach($items as $item) {
1039                                         $x = $item->getAttribute('rel');
1040                                         if($x == "lrdd") {
1041                                                 $link = $item->getAttribute('href');
1042                                                 break;
1043                                         }
1044                                 }
1045                         }
1046                 }
1047
1048                 if(isset($link))
1049                         return(fetch_xrd_links($link));
1050         }
1051         return array();
1052 }}
1053
1054
1055
1056 // Given a host name, locate the LRDD template from that
1057 // host. Returns the LRDD template or an empty string on
1058 // error/failure.
1059
1060 if(! function_exists('fetch_lrdd_template')) {
1061 function fetch_lrdd_template($host) {
1062         $tpl = '';
1063         $url = 'http://' . $host . '/.well-known/host-meta' ;
1064         $links = fetch_xrd_links($url);
1065         if(count($links)) {
1066                 foreach($links as $link)
1067                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
1068                                 $tpl = $link['@attributes']['template'];
1069         }
1070         if(! strpos($tpl,'{uri}'))
1071                 $tpl = '';
1072         return $tpl;
1073 }}
1074
1075 // Given a URL, retrieve the page as an XRD document.
1076 // Return an array of links.
1077 // on error/failure return empty array.
1078
1079 if(! function_exists('fetch_xrd_links')) {
1080 function fetch_xrd_links($url) {
1081
1082
1083         $xml = fetch_url($url);
1084         if (! $xml)
1085                 return array();
1086
1087         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
1088         $h = simplexml_load_string($xml);
1089         $arr = convert_xml_element_to_array($h);
1090
1091         if(isset($arr['xrd']['link'])) {
1092                 $link = $arr['xrd']['link'];
1093                 if(! isset($link[0]))
1094                         $links = array($link);
1095                 else
1096                         $links = $link;
1097                 return $links;
1098         }
1099         return array();
1100 }}
1101
1102 // Convert an ACL array to a storable string
1103
1104 if(! function_exists('perms2str')) {
1105 function perms2str($p) {
1106         $ret = '';
1107         $tmp = $p;
1108         if(is_array($tmp)) {
1109                 array_walk($tmp,'sanitise_acl');
1110                 $ret = implode('',$tmp);
1111         }
1112         return $ret;
1113 }}
1114
1115 // generate a guaranteed unique (for this domain) item ID for ATOM
1116 // safe from birthday paradox
1117
1118 if(! function_exists('item_new_uri')) {
1119 function item_new_uri($hostname,$uid) {
1120
1121         do {
1122                 $dups = false;
1123                 $hash = random_string();
1124
1125                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
1126
1127                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
1128                         dbesc($uri));
1129                 if(count($r))
1130                         $dups = true;
1131         } while($dups == true);
1132         return $uri;
1133 }}
1134
1135 // Generate a guaranteed unique photo ID.
1136 // safe from birthday paradox
1137
1138 if(! function_exists('photo_new_resource')) {
1139 function photo_new_resource() {
1140
1141         do {
1142                 $found = false;
1143                 $resource = hash('md5',uniqid(mt_rand(),true));
1144                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1145                         dbesc($resource)
1146                 );
1147                 if(count($r))
1148                         $found = true;
1149         } while($found == true);
1150         return $resource;
1151 }}
1152
1153
1154 // Take a URL from the wild, prepend http:// if necessary
1155 // and check DNS to see if it's real
1156 // return true if it's OK, false if something is wrong with it
1157
1158 if(! function_exists('validate_url')) {
1159 function validate_url(&$url) {
1160         if(substr($url,0,4) != 'http')
1161                 $url = 'http://' . $url;
1162         $h = parse_url($url);
1163
1164         if(($h) && (checkdnsrr($h['host'], 'ANY'))) {
1165                 return true;
1166         }
1167         return false;
1168 }}
1169
1170 // checks that email is an actual resolvable internet address
1171
1172 if(! function_exists('validate_email')) {
1173 function validate_email($addr) {
1174
1175         if(! strpos($addr,'@'))
1176                 return false;
1177         $h = substr($addr,strpos($addr,'@') + 1);
1178
1179         if(($h) && (checkdnsrr($h, 'ANY'))) {
1180                 return true;
1181         }
1182         return false;
1183 }}
1184
1185 // Check $url against our list of allowed sites,
1186 // wildcards allowed. If allowed_sites is unset return true;
1187 // If url is allowed, return true.
1188 // otherwise, return false
1189
1190 if(! function_exists('allowed_url')) {
1191 function allowed_url($url) {
1192
1193         $h = parse_url($url);
1194
1195         if(! $h) {
1196                 return false;
1197         }
1198
1199         $str_allowed = get_config('system','allowed_sites');
1200         if(! $str_allowed)
1201                 return true;
1202
1203         $found = false;
1204
1205         $host = strtolower($h['host']);
1206
1207         // always allow our own site
1208
1209         if($host == strtolower($_SERVER['SERVER_NAME']))
1210                 return true;
1211
1212         $fnmatch = function_exists('fnmatch');
1213         $allowed = explode(',',$str_allowed);
1214
1215         if(count($allowed)) {
1216                 foreach($allowed as $a) {
1217                         $pat = strtolower(trim($a));
1218                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1219                                 $found = true; 
1220                                 break;
1221                         }
1222                 }
1223         }
1224         return $found;
1225 }}
1226
1227 // check if email address is allowed to register here.
1228 // Compare against our list (wildcards allowed).
1229 // Returns false if not allowed, true if allowed or if
1230 // allowed list is not configured.
1231
1232 if(! function_exists('allowed_email')) {
1233 function allowed_email($email) {
1234
1235
1236         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1237         if(! $domain)
1238                 return false;
1239
1240         $str_allowed = get_config('system','allowed_email');
1241         if(! $str_allowed)
1242                 return true;
1243
1244         $found = false;
1245
1246         $fnmatch = function_exists('fnmatch');
1247         $allowed = explode(',',$str_allowed);
1248
1249         if(count($allowed)) {
1250                 foreach($allowed as $a) {
1251                         $pat = strtolower(trim($a));
1252                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1253                                 $found = true; 
1254                                 break;
1255                         }
1256                 }
1257         }
1258         return $found;
1259 }}
1260
1261 // Format the like/dislike text for a profile item
1262 // $cnt = number of people who like/dislike the item
1263 // $arr = array of pre-linked names of likers/dislikers
1264 // $type = one of 'like, 'dislike'
1265 // $id  = item id
1266 // returns formatted text
1267
1268 if(! function_exists('format_like')) {
1269 function format_like($cnt,$arr,$type,$id) {
1270         $o = '';
1271         if($cnt == 1)
1272                 $o .= $arr[0] . (($type === 'like') ? t(' likes this.') : t(' doesn\'t like this.')) . EOL ;
1273         else {
1274                 $o .= '<span class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');" >' 
1275                         . $cnt . ' ' . t('people') . '</span> ' . (($type === 'like') ? t('like this.') : t('don\'t like this.')) . EOL ;
1276                 $total = count($arr);
1277                 if($total >= MAX_LIKERS)
1278                         $arr = array_slice($arr, 0, MAX_LIKERS - 1);
1279                 if($total < MAX_LIKERS)
1280                         $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1];
1281                 $str = implode(', ', $arr);
1282                 if($total >= MAX_LIKERS)
1283                         $str .= t(', and ') . $total - MAX_LIKERS . t(' other people');
1284                 $str .= (($type === 'like') ? t(' like this.') : t(' don\'t like this.'));
1285                 $o .= "\t" . '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>';
1286         }
1287         return $o;
1288 }}
1289
1290
1291 // wrapper to load a view template, checking for alternate
1292 // languages before falling back to the default
1293
1294 if(! function_exists('load_view_file')) {
1295 function load_view_file($s) {
1296         $b = basename($s);
1297         $d = dirname($s);
1298         $lang = get_config('system','language');
1299         if($lang === false)
1300                 $lang = 'en';
1301         if(file_exists("$d/$lang/$b"))
1302                 return file_get_contents("$d/$lang/$b");
1303         return file_get_contents($s);
1304 }}
1305
1306 // for html,xml parsing - let's say you've got
1307 // an attribute foobar="class1 class2 class3"
1308 // and you want to find out if it contains 'class3'.
1309 // you can't use a normal sub string search because you
1310 // might match 'notclass3' and a regex to do the job is 
1311 // possible but a bit complicated. 
1312 // pass the attribute string as $attr and the attribute you 
1313 // are looking for as $s - returns true if found, otherwise false
1314
1315 if(! function_exists('attribute_contains')) {
1316 function attribute_contains($attr,$s) {
1317         $a = explode(' ', $attr);
1318         if(count($a) && in_array($s,$a))
1319                 return true;
1320         return false;
1321 }}
1322
1323 if(! function_exists('logger')) {
1324 function logger($msg,$level = 0) {
1325
1326         $debugging = get_config('system','debugging');
1327         $loglevel  = intval(get_config('system','loglevel'));
1328         $logfile   = get_config('system','logfile');
1329
1330         if((! $debugging) || (! $logfile) || ($level > $loglevel))
1331                 return;
1332         
1333         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
1334         return;
1335 }}
1336
1337
1338 if(! function_exists('activity_match')) {
1339 function activity_match($haystack,$needle) {
1340         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
1341                 return true;
1342         return false;
1343 }}
1344
1345
1346 // Pull out all #hashtags and @person tags from $s;
1347 // We also get @person@domain.com - which would make 
1348 // the regex quite complicated as tags can also
1349 // end a sentence. So we'll run through our results
1350 // and strip the period from any tags which end with one.
1351 // Returns array of tags found, or empty array.
1352
1353
1354 if(! function_exists('get_tags')) {
1355 function get_tags($s) {
1356         $ret = array();
1357         if(preg_match_all('/([@#][^ ,:?]*)([ ,:?]|$)/',$s,$match)) {
1358                 foreach($match[1] as $match) {
1359                         if(substr($match,-1,1) === '.')
1360                                 $ret[] = substr($match,0,-1);
1361                         else
1362                                 $ret[] = $match;
1363                 }
1364         }
1365
1366         return $ret;
1367 }}
1368
1369
1370 // quick and dirty quoted_printable encoding
1371
1372 if(! function_exists('qp')) {
1373 function qp($s) {
1374 return str_replace ("%","=",rawurlencode($s));
1375 }} 
1376
1377
1378 if(! function_exists('like_puller')) {
1379 function like_puller($a,$item,&$arr,$mode) {
1380
1381         $url = '';
1382         $sparkle = '';
1383         $verb = (($mode === 'like') ? ACTIVITY_LIKE : ACTIVITY_DISLIKE);
1384
1385         if((activity_match($item['verb'],$verb)) && ($item['id'] != $item['parent'])) {
1386                 $url = $item['author-link'];
1387                 if(($item['network'] === 'dfrn') && (! $item['self']) && ($item['author-link'] == $item['url'])) {
1388                         $url = $a->get_baseurl() . '/redir/' . $item['contact-id'];
1389                         $sparkle = ' class="sparkle" ';
1390                 }
1391                 if(! ((isset($arr[$item['parent'] . '-l'])) && (is_array($arr[$item['parent'] . '-l']))))
1392                         $arr[$item['parent'] . '-l'] = array();
1393                 if(! isset($arr[$item['parent']]))
1394                         $arr[$item['parent']] = 1;
1395                 else    
1396                         $arr[$item['parent']] ++;
1397                 $arr[$item['parent'] . '-l'][] = '<a href="'. $url . '"'. $sparkle .'>' . $item['author-name'] . '</a>';
1398         }
1399         return;
1400 }}
1401
1402 if(! function_exists('get_mentions')) {
1403 function get_mentions($item) {
1404         $o = '';
1405         if(! strlen($item['tag']))
1406                 return $o;
1407
1408         $arr = explode(',',$item['tag']);
1409         foreach($arr as $x) {
1410                 $matches = null;
1411                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches))
1412                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
1413         }
1414         return $o;
1415 }}
1416
1417 if(! function_exists('contact_block')) {
1418 function contact_block() {
1419         $o = '';
1420         $a = get_app();
1421         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
1422                 return $o;
1423         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
1424                         intval($a->profile['uid'])
1425         );
1426         if(count($r)) {
1427                 $total = intval($r[0]['total']);
1428         }
1429         if(! $total) {
1430                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
1431                 return $o;
1432         }
1433         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT 24",
1434                         intval($a->profile['uid'])
1435         );
1436         if(count($r)) {
1437                 $o .= '<h4 class="contact-h4">' . $total . ' ' . t('Contacts') . '</h4><div id="contact-block">';
1438                 foreach($r as $rr) {
1439                         $redirect_url = $a->get_baseurl() . '/redir/' . $rr['id'];
1440                         if(local_user() && ($rr['uid'] == local_user())
1441                                 && ($rr['network'] === 'dfrn')) {
1442                                 $url = $redirect_url;
1443                                 $sparkle = ' sparkle';
1444                         }
1445                         else {
1446                                 $url = $rr['url'];
1447                                 $sparkle = '';
1448                         }
1449
1450                         $o .= '<div class="contact-block-div"><a class="contact-block-link' . $sparkle . '" href="' . $url . '" ><img class="contact-block-img' . $sparkle . '" src="' . $rr['micro'] . '" title="' . $rr['name'] . ' [' . $rr['url'] . ']" alt="' . $rr['name'] . '" /></a></div>' . "\r\n";
1451                 }
1452                 $o .= '</div><div id="contact-block-end"></div>';
1453                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
1454                 
1455         }
1456         return $o;
1457
1458 }}
1459
1460 if(! function_exists('search')) {
1461 function search($s) {
1462         $a = get_app();
1463         $o  = '<div id="search-box">';
1464         $o .= '<form action="' . $a->get_baseurl() . '/search' . '" method="get" >';
1465         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
1466         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
1467         $o .= '</form></div>';
1468         return $o;
1469 }}
1470
1471 if(! function_exists('valid_email')) {
1472 function valid_email($x){
1473         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
1474                 return true;
1475         return false;
1476 }}
1477
1478
1479 if(! function_exists('gravatar_img')) {
1480 function gravatar_img($email) {
1481         $size = 175;
1482         $opt = 'identicon';   // psuedo-random geometric pattern if not found
1483         $rating = 'pg';
1484         $hash = md5(trim(strtolower($email)));
1485         
1486         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
1487                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
1488
1489         logger('gravatar: ' . $email . ' ' . $url);
1490         return $url;
1491 }}
1492