]> git.mxchange.org Git - friendica.git/blob - boot.php
don't invoke slap if there's no endpoint configured
[friendica.git] / boot.php
1 <?php
2
3 set_time_limit(0);
4
5 define ( 'BUILD_ID',               1017   );
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         if($curl_time)
382                 curl_setopt($ch, CURLOPT_TIMEOUT, $curl_time);
383
384         // by default we will allow self-signed certs
385         // but you can override this
386
387         $check_cert = get_config('system','verifyssl');
388         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
389
390         $prx = get_config('system','proxy');
391         if(strlen($prx)) {
392                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
393                 curl_setopt($ch, CURLOPT_PROXY, $prx);
394                 $prxusr = get_config('system','proxyuser');
395                 if(strlen($prxusr))
396                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
397         }
398         if($binary)
399                 curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
400
401         $s = curl_exec($ch);
402
403         $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
404         $header = substr($s,0,strpos($s,"\r\n\r\n"));
405         if(stristr($header,'100') && (strlen($header) < 30)) {
406                 // 100 Continue has two headers, get the real one
407                 $s = substr($s,strlen($header)+4);
408                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
409         }
410         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
411         $matches = array();
412         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
413         $url = trim(array_pop($matches));
414         $url_parsed = parse_url($url);
415         if (isset($url_parsed)) {
416             $redirects++;
417             return fetch_url($url,$binary,$redirects);
418         }
419     }
420         $a = get_app();
421         $a->set_curl_code($http_code);
422         $body = substr($s,strlen($header)+4);
423         $a->set_curl_headers($header);
424
425         curl_close($ch);
426         return($body);
427 }}
428
429 // post request to $url. $params is an array of post variables.
430
431 if(! function_exists('post_url')) {
432 function post_url($url,$params, $headers = null, &$redirects = 0) {
433         $ch = curl_init($url);
434         if(($redirects > 8) || (! $ch)) 
435                 return false;
436
437         curl_setopt($ch, CURLOPT_HEADER, true);
438         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
439         curl_setopt($ch, CURLOPT_POST,1);
440         curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
441
442         $curl_time = intval(get_config('system','curl_timeout'));
443         if($curl_time)
444                 curl_setopt($ch, CURLOPT_TIMEOUT, $curl_time);
445
446         if(is_array($headers))
447                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
448
449         $check_cert = get_config('system','verifyssl');
450         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
451         $prx = get_config('system','proxy');
452         if(strlen($prx)) {
453                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
454                 curl_setopt($ch, CURLOPT_PROXY, $prx);
455                 $prxusr = get_config('system','proxyuser');
456                 if(strlen($prxusr))
457                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
458         }
459
460         $s = curl_exec($ch);
461
462         $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
463         $header = substr($s,0,strpos($s,"\r\n\r\n"));
464         if(stristr($header,'100') && (strlen($header) < 30)) {
465                 // 100 Continue has two headers, get the real one
466                 $s = substr($s,strlen($header)+4);
467                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
468         }
469         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
470         $matches = array();
471         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
472         $url = trim(array_pop($matches));
473         $url_parsed = parse_url($url);
474         if (isset($url_parsed)) {
475             $redirects++;
476             return post_url($url,$binary,$headers,$redirects);
477         }
478     }
479         $a = get_app();
480         $a->set_curl_code($http_code);
481         $body = substr($s,strlen($header)+4);
482         $a->set_curl_headers($header);
483
484         curl_close($ch);
485         return($body);
486 }}
487
488 // random hash, 64 chars
489
490 if(! function_exists('random_string')) {
491 function random_string() {
492         return(hash('sha256',uniqid(rand(),true)));
493 }}
494
495 // This is our primary input filter. The high bit hack only involved some old
496 // IE browser, forget which. 
497 // Use this on any text input where angle chars are not valid or permitted
498 // They will be replaced with safer brackets. This may be filtered further
499 // if these are not allowed either.   
500
501 if(! function_exists('notags')) {
502 function notags($string) {
503         // protect against :<> with high-bit set
504         return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
505 }}
506
507 // use this on "body" or "content" input where angle chars shouldn't be removed,
508 // and allow them to be safely displayed.
509
510 if(! function_exists('escape_tags')) {
511 function escape_tags($string) {
512
513         return(htmlspecialchars($string));
514 }}
515
516 // wrapper for adding a login box. If $register == true provide a registration
517 // link. This will most always depend on the value of $a->config['register_policy'].
518 // returns the complete html for inserting into the page
519
520 if(! function_exists('login')) {
521 function login($register = false) {
522         $o = "";
523         $register_html = (($register) ? load_view_file("view/register-link.tpl") : "");
524
525
526         if(x($_SESSION,'authenticated')) {
527                 $o = load_view_file("view/logout.tpl");
528         }
529         else {
530                 $o = load_view_file("view/login.tpl");
531
532                 $o = replace_macros($o,array('$register_html' => $register_html ));
533         }
534         return $o;
535 }}
536
537 // generate a string that's random, but usually pronounceable. 
538 // used to generate initial passwords
539
540 if(! function_exists('autoname')) {
541 function autoname($len) {
542
543         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u'); 
544         if(mt_rand(0,5) == 4)
545                 $vowels[] = 'y';
546
547         $cons = array(
548                         'b','bl','br',
549                         'c','ch','cl','cr',
550                         'd','dr',
551                         'f','fl','fr',
552                         'g','gh','gl','gr',
553                         'h',
554                         'j',
555                         'k','kh','kl','kr',
556                         'l',
557                         'm',
558                         'n',
559                         'p','ph','pl','pr',
560                         'qu',
561                         'r','rh',
562                         's','sc','sh','sm','sp','st',
563                         't','th','tr',
564                         'v',
565                         'w','wh',
566                         'x',
567                         'z','zh'
568                         );
569
570         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
571                                 'nd','ng','nk','nt','rn','rp','rt');
572
573         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
574                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
575
576         $start = mt_rand(0,2);
577         if($start == 0)
578                 $table = $vowels;
579         else
580                 $table = $cons;
581
582         $word = '';
583
584         for ($x = 0; $x < $len; $x ++) {
585                 $r = mt_rand(0,count($table) - 1);
586                 $word .= $table[$r];
587   
588                 if($table == $vowels)
589                         $table = array_merge($cons,$midcons);
590                 else
591                         $table = $vowels;
592
593         }
594
595         $word = substr($word,0,$len);
596
597         foreach($noend as $noe) {
598                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
599                         $word = substr($word,0,-1);
600                         break;
601                 }
602         }
603         if(substr($word,-1) == 'q')
604                 $word = substr($word,0,-1);    
605         return $word;
606 }}
607
608 // Used to end the current process, after saving session state. 
609
610 if(! function_exists('killme')) {
611 function killme() {
612         session_write_close();
613         exit;
614 }}
615
616 // redirect to another URL and terminate this process.
617
618 if(! function_exists('goaway')) {
619 function goaway($s) {
620         header("Location: $s");
621         killme();
622 }}
623
624 // Generic XML return
625 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable 
626 // of $st and an optional text <message> of $message and terminates the current process. 
627
628 if(! function_exists('xml_status')) {
629 function xml_status($st, $message = '') {
630
631         $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
632
633         if($st)
634                 logger('xml_status returning non_zero: ' . $st . " message=" . $message);
635
636         header( "Content-type: text/xml" );
637         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
638         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
639         killme();
640 }}
641
642 // Returns the uid of locally logged in user or false.
643
644 if(! function_exists('local_user')) {
645 function local_user() {
646         if((x($_SESSION,'authenticated')) && (x($_SESSION,'uid')))
647                 return intval($_SESSION['uid']);
648         return false;
649 }}
650
651 // Returns contact id of authenticated site visitor or false
652
653 if(! function_exists('remote_user')) {
654 function remote_user() {
655         if((x($_SESSION,'authenticated')) && (x($_SESSION,'visitor_id')))
656                 return intval($_SESSION['visitor_id']);
657         return false;
658 }}
659
660 // contents of $s are displayed prominently on the page the next time
661 // a page is loaded. Usually used for errors or alerts.
662
663 if(! function_exists('notice')) {
664 function notice($s) {
665         $a = get_app();
666         if($a->interactive)
667                 $_SESSION['sysmsg'] .= $s;
668 }}
669
670 // wrapper around config to limit the text length of an incoming message
671
672 if(! function_exists('get_max_import_size')) {
673 function get_max_import_size() {
674         global $a;
675         return ((x($a->config,'max_import_size')) ? $a->config['max_import_size'] : 0 );
676 }}
677
678
679 // escape text ($str) for XML transport
680 // returns escaped text.
681
682 if(! function_exists('xmlify')) {
683 function xmlify($str) {
684         $buffer = '';
685         
686         for($x = 0; $x < strlen($str); $x ++) {
687                 $char = $str[$x];
688         
689                 switch( $char ) {
690
691                         case "\r" :
692                                 break;
693                         case "&" :
694                                 $buffer .= '&amp;';
695                                 break;
696                         case "'" :
697                                 $buffer .= '&apos;';
698                                 break;
699
700                         case "\"" :
701                                 $buffer .= '&quot;';
702                                 break;
703                         case '<' :
704                                 $buffer .= '&lt;';
705                                 break;
706                         case '>' :
707                                 $buffer .= '&gt;';
708                                 break;
709                         case "\n" :
710                                 $buffer .= "\n";
711                                 break;
712                         default :
713                                 $buffer .= $char;
714                                 break;
715                 }       
716         }
717         $buffer = trim($buffer);
718         return($buffer);
719 }}
720
721 // undo an xmlify
722 // pass xml escaped text ($s), returns unescaped text
723
724 if(! function_exists('unxmlify')) {
725 function unxmlify($s) {
726         $ret = str_replace('&amp;','&', $s);
727         $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
728         return $ret;    
729 }}
730
731 // convenience wrapper, reverse the operation "bin2hex"
732
733 if(! function_exists('hex2bin')) {
734 function hex2bin($s) {
735         return(pack("H*",$s));
736 }}
737
738 // Automatic pagination.
739 // To use, get the count of total items.
740 // Then call $a->set_pager_total($number_items);
741 // Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
742 // Then call paginate($a) after the end of the display loop to insert the pager block on the page
743 // (assuming there are enough items to paginate).
744 // When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
745 // will limit the results to the correct items for the current page. 
746 // The actual page handling is then accomplished at the application layer. 
747
748 if(! function_exists('paginate')) {
749 function paginate(&$a) {
750         $o = '';
751         $stripped = preg_replace('/(&page=[0-9]*)/','',$_SERVER['QUERY_STRING']);
752         $stripped = str_replace('q=','',$stripped);
753         $stripped = trim($stripped,'/');
754         $url = $a->get_baseurl() . '/' . $stripped;
755
756
757           if($a->pager['total'] > $a->pager['itemspage']) {
758                 $o .= '<div class="pager">';
759                 if($a->pager['page'] != 1)
760                         $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
761
762                 $o .=  "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
763
764                 $numpages = $a->pager['total'] / $a->pager['itemspage'];
765
766                 $numstart = 1;
767                 $numstop = $numpages;
768
769                 if($numpages > 14) {
770                         $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
771                         $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
772                 }
773    
774                 for($i = $numstart; $i <= $numstop; $i++){
775                         if($i == $a->pager['page'])
776                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
777                         else
778                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
779                         $o .= '</span> ';
780                 }
781
782                 if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
783                         if($i == $a->pager['page'])
784                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
785                         else
786                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
787                         $o .= '</span> ';
788                 }
789
790                 $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
791                 $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
792
793                 if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
794                         $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
795                 $o .= '</div>'."\r\n";
796         }
797         return $o;
798 }}
799
800 // Turn user/group ACLs stored as angle bracketed text into arrays
801
802 if(! function_exists('expand_acl')) {
803 function expand_acl($s) {
804         // turn string array of angle-bracketed elements into numeric array
805         // e.g. "<1><2><3>" => array(1,2,3);
806         $ret = array();
807
808         if(strlen($s)) {
809                 $t = str_replace('<','',$s);
810                 $a = explode('>',$t);
811                 foreach($a as $aa) {
812                         if(intval($aa))
813                                 $ret[] = intval($aa);
814                 }
815         }
816         return $ret;
817 }}              
818
819 // Used to wrap ACL elements in angle brackets for storage 
820
821 if(! function_exists('sanitise_acl')) {
822 function sanitise_acl(&$item) {
823         if(intval($item))
824                 $item = '<' . intval(notags(trim($item))) . '>';
825         else
826                 unset($item);
827 }}
828
829 // retrieve a "family" of config variables from database to cached storage
830
831 if(! function_exists('load_config')) {
832 function load_config($family) {
833         global $a;
834         $r = q("SELECT * FROM `config` WHERE `cat` = '%s'",
835                 dbesc($family)
836         );
837         if(count($r)) {
838                 foreach($r as $rr) {
839                         $k = $rr['k'];
840                         $a->config[$family][$k] = $rr['v'];
841                 }
842         }
843 }}
844
845 // get a particular config variable given the family name
846 // and key. Returns false if not set.
847 // $instore is only used by the set_config function
848 // to determine if the key already exists in the DB
849 // If a key is found in the DB but doesn't exist in
850 // local config cache, pull it into the cache so we don't have
851 // to hit the DB again for this item.
852
853 if(! function_exists('get_config')) {
854 function get_config($family, $key, $instore = false) {
855
856         global $a;
857
858         if(! $instore) {
859                 if(isset($a->config[$family][$key])) {
860                         if($a->config[$family][$key] === '!<unset>!') {
861                                 return false;
862                         }
863                         return $a->config[$family][$key];
864                 }
865         }
866         $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
867                 dbesc($family),
868                 dbesc($key)
869         );
870         if(count($ret)) {
871                 $a->config[$family][$key] = $ret[0]['v'];
872                 return $ret[0]['v'];
873         }
874         else {
875                 $a->config[$family][$key] = '!<unset>!';
876         }
877         return false;
878 }}
879
880 // Store a config value ($value) in the category ($family)
881 // under the key ($key)
882 // Return the value, or false if the database update failed
883
884 if(! function_exists('set_config')) {
885 function set_config($family,$key,$value) {
886
887         global $a;
888         $a->config[$family][$key] = $value;
889
890         if(get_config($family,$key,true) === false) {
891                 $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
892                         dbesc($family),
893                         dbesc($key),
894                         dbesc($value)
895                 );
896                 if($ret) 
897                         return $value;
898                 return $ret;
899         }
900         $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
901                 dbesc($value),
902                 dbesc($family),
903                 dbesc($key)
904         );
905         if($ret)
906                 return $value;
907         return $ret;
908 }}
909
910 // convert an XML document to a normalised, case-corrected array
911 // used by webfinger
912
913 if(! function_exists('convert_xml_element_to_array')) {
914 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
915
916         // If we're getting too deep, bail out
917         if ($recursion_depth > 512) {
918                 return(null);
919         }
920
921         if (!is_string($xml_element) &&
922         !is_array($xml_element) &&
923         (get_class($xml_element) == 'SimpleXMLElement')) {
924                 $xml_element_copy = $xml_element;
925                 $xml_element = get_object_vars($xml_element);
926         }
927
928         if (is_array($xml_element)) {
929                 $result_array = array();
930                 if (count($xml_element) <= 0) {
931                         return (trim(strval($xml_element_copy)));
932                 }
933
934                 foreach($xml_element as $key=>$value) {
935
936                         $recursion_depth++;
937                         $result_array[strtolower($key)] =
938                 convert_xml_element_to_array($value, $recursion_depth);
939                         $recursion_depth--;
940                 }
941                 if ($recursion_depth == 0) {
942                         $temp_array = $result_array;
943                         $result_array = array(
944                                 strtolower($xml_element_copy->getName()) => $temp_array,
945                         );
946                 }
947
948                 return ($result_array);
949
950         } else {
951                 return (trim(strval($xml_element)));
952         }
953 }}
954
955 // Given an email style address, perform webfinger lookup and 
956 // return the resulting DFRN profile URL, or if no DFRN profile URL
957 // is located, returns an OStatus subscription template (prefixed 
958 // with the string 'stat:' to identify it as on OStatus template).
959 // If this isn't an email style address just return $s.
960 // Return an empty string if email-style addresses but webfinger fails,
961 // or if the resultant personal XRD doesn't contain a supported 
962 // subscription/friend-request attribute.
963
964 if(! function_exists('webfinger_dfrn')) {
965 function webfinger_dfrn($s) {
966         if(! strstr($s,'@')) {
967                 return $s;
968         }
969         $links = webfinger($s);
970         logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
971         if(count($links)) {
972                 foreach($links as $link)
973                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
974                                 return $link['@attributes']['href'];
975                 foreach($links as $link)
976                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
977                                 return 'stat:' . $link['@attributes']['template'];              
978         }
979         return '';
980 }}
981
982 // Given an email style address, perform webfinger lookup and 
983 // return the array of link attributes from the personal XRD file.
984 // On error/failure return an empty array.
985
986
987 if(! function_exists('webfinger')) {
988 function webfinger($s) {
989         $host = '';
990         if(strstr($s,'@')) {
991                 $host = substr($s,strpos($s,'@') + 1);
992         }
993         if(strlen($host)) {
994                 $tpl = fetch_lrdd_template($host);
995                 logger('webfinger: lrdd template: ' . $tpl);
996                 if(strlen($tpl)) {
997                         $pxrd = str_replace('{uri}', urlencode('acct:'.$s), $tpl);
998                         $links = fetch_xrd_links($pxrd);
999                         if(! count($links)) {
1000                                 // try with double slashes
1001                                 $pxrd = str_replace('{uri}', urlencode('acct://'.$s), $tpl);
1002                                 $links = fetch_xrd_links($pxrd);
1003                         }
1004                         return $links;
1005                 }
1006         }
1007         return array();
1008 }}
1009
1010 if(! function_exists('lrdd')) {
1011 function lrdd($uri) {
1012
1013         $a = get_app();
1014
1015         if(strstr($uri,'@')) {  
1016                 return(webfinger($uri));
1017         }
1018         else {
1019                 $html = fetch_url($uri);
1020                 $headers = $a->get_curl_headers();
1021                 $lines = explode("\n",$headers);
1022                 if(count($lines)) {
1023                         foreach($lines as $line) {                              
1024                                 // TODO alter the following regex to support multiple relations (space separated)
1025                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1026                                         $link = $matches[1];
1027                                         break;
1028                                 }
1029                         }
1030                 }
1031                 if(! isset($link)) {
1032                         // parse the page of the supplied URL looking for rel links
1033
1034                         require_once('library/HTML5/Parser.php');
1035                         $dom = HTML5_Parser::parse($html);
1036
1037                         if($dom) {
1038                                 $items = $dom->getElementsByTagName('link');
1039
1040                                 foreach($items as $item) {
1041                                         $x = $item->getAttribute('rel');
1042                                         if($x == "lrdd") {
1043                                                 $link = $item->getAttribute('href');
1044                                                 break;
1045                                         }
1046                                 }
1047                         }
1048                 }
1049
1050                 if(isset($link))
1051                         return(fetch_xrd_links($link));
1052         }
1053         return array();
1054 }}
1055
1056
1057
1058 // Given a host name, locate the LRDD template from that
1059 // host. Returns the LRDD template or an empty string on
1060 // error/failure.
1061
1062 if(! function_exists('fetch_lrdd_template')) {
1063 function fetch_lrdd_template($host) {
1064         $tpl = '';
1065         $url = 'http://' . $host . '/.well-known/host-meta' ;
1066         $links = fetch_xrd_links($url);
1067         if(count($links)) {
1068                 foreach($links as $link)
1069                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
1070                                 $tpl = $link['@attributes']['template'];
1071         }
1072         if(! strpos($tpl,'{uri}'))
1073                 $tpl = '';
1074         return $tpl;
1075 }}
1076
1077 // Given a URL, retrieve the page as an XRD document.
1078 // Return an array of links.
1079 // on error/failure return empty array.
1080
1081 if(! function_exists('fetch_xrd_links')) {
1082 function fetch_xrd_links($url) {
1083
1084
1085         $xml = fetch_url($url);
1086         if (! $xml)
1087                 return array();
1088
1089         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
1090         $h = simplexml_load_string($xml);
1091         $arr = convert_xml_element_to_array($h);
1092
1093         if(isset($arr['xrd']['link'])) {
1094                 $link = $arr['xrd']['link'];
1095                 if(! isset($link[0]))
1096                         $links = array($link);
1097                 else
1098                         $links = $link;
1099                 return $links;
1100         }
1101         return array();
1102 }}
1103
1104 // Convert an ACL array to a storable string
1105
1106 if(! function_exists('perms2str')) {
1107 function perms2str($p) {
1108         $ret = '';
1109         $tmp = $p;
1110         if(is_array($tmp)) {
1111                 array_walk($tmp,'sanitise_acl');
1112                 $ret = implode('',$tmp);
1113         }
1114         return $ret;
1115 }}
1116
1117 // generate a guaranteed unique (for this domain) item ID for ATOM
1118 // safe from birthday paradox
1119
1120 if(! function_exists('item_new_uri')) {
1121 function item_new_uri($hostname,$uid) {
1122
1123         do {
1124                 $dups = false;
1125                 $hash = random_string();
1126
1127                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
1128
1129                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
1130                         dbesc($uri));
1131                 if(count($r))
1132                         $dups = true;
1133         } while($dups == true);
1134         return $uri;
1135 }}
1136
1137 // Generate a guaranteed unique photo ID.
1138 // safe from birthday paradox
1139
1140 if(! function_exists('photo_new_resource')) {
1141 function photo_new_resource() {
1142
1143         do {
1144                 $found = false;
1145                 $resource = hash('md5',uniqid(mt_rand(),true));
1146                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1147                         dbesc($resource)
1148                 );
1149                 if(count($r))
1150                         $found = true;
1151         } while($found == true);
1152         return $resource;
1153 }}
1154
1155
1156 // Take a URL from the wild, prepend http:// if necessary
1157 // and check DNS to see if it's real
1158 // return true if it's OK, false if something is wrong with it
1159
1160 if(! function_exists('validate_url')) {
1161 function validate_url(&$url) {
1162         if(substr($url,0,4) != 'http')
1163                 $url = 'http://' . $url;
1164         $h = parse_url($url);
1165
1166         if(($h) && (checkdnsrr($h['host'], 'ANY'))) {
1167                 return true;
1168         }
1169         return false;
1170 }}
1171
1172 // checks that email is an actual resolvable internet address
1173
1174 if(! function_exists('validate_email')) {
1175 function validate_email($addr) {
1176
1177         if(! strpos($addr,'@'))
1178                 return false;
1179         $h = substr($addr,strpos($addr,'@') + 1);
1180
1181         if(($h) && (checkdnsrr($h, 'ANY'))) {
1182                 return true;
1183         }
1184         return false;
1185 }}
1186
1187 // Check $url against our list of allowed sites,
1188 // wildcards allowed. If allowed_sites is unset return true;
1189 // If url is allowed, return true.
1190 // otherwise, return false
1191
1192 if(! function_exists('allowed_url')) {
1193 function allowed_url($url) {
1194
1195         $h = parse_url($url);
1196
1197         if(! $h) {
1198                 return false;
1199         }
1200
1201         $str_allowed = get_config('system','allowed_sites');
1202         if(! $str_allowed)
1203                 return true;
1204
1205         $found = false;
1206
1207         $host = strtolower($h['host']);
1208
1209         // always allow our own site
1210
1211         if($host == strtolower($_SERVER['SERVER_NAME']))
1212                 return true;
1213
1214         $fnmatch = function_exists('fnmatch');
1215         $allowed = explode(',',$str_allowed);
1216
1217         if(count($allowed)) {
1218                 foreach($allowed as $a) {
1219                         $pat = strtolower(trim($a));
1220                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1221                                 $found = true; 
1222                                 break;
1223                         }
1224                 }
1225         }
1226         return $found;
1227 }}
1228
1229 // check if email address is allowed to register here.
1230 // Compare against our list (wildcards allowed).
1231 // Returns false if not allowed, true if allowed or if
1232 // allowed list is not configured.
1233
1234 if(! function_exists('allowed_email')) {
1235 function allowed_email($email) {
1236
1237
1238         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1239         if(! $domain)
1240                 return false;
1241
1242         $str_allowed = get_config('system','allowed_email');
1243         if(! $str_allowed)
1244                 return true;
1245
1246         $found = false;
1247
1248         $fnmatch = function_exists('fnmatch');
1249         $allowed = explode(',',$str_allowed);
1250
1251         if(count($allowed)) {
1252                 foreach($allowed as $a) {
1253                         $pat = strtolower(trim($a));
1254                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1255                                 $found = true; 
1256                                 break;
1257                         }
1258                 }
1259         }
1260         return $found;
1261 }}
1262
1263 // Format the like/dislike text for a profile item
1264 // $cnt = number of people who like/dislike the item
1265 // $arr = array of pre-linked names of likers/dislikers
1266 // $type = one of 'like, 'dislike'
1267 // $id  = item id
1268 // returns formatted text
1269
1270 if(! function_exists('format_like')) {
1271 function format_like($cnt,$arr,$type,$id) {
1272         $o = '';
1273         if($cnt == 1)
1274                 $o .= $arr[0] . (($type === 'like') ? t(' likes this.') : t(' doesn\'t like this.')) . EOL ;
1275         else {
1276                 $o .= '<span class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');" >' 
1277                         . $cnt . ' ' . t('people') . '</span> ' . (($type === 'like') ? t('like this.') : t('don\'t like this.')) . EOL ;
1278                 $total = count($arr);
1279                 if($total >= MAX_LIKERS)
1280                         $arr = array_slice($arr, 0, MAX_LIKERS - 1);
1281                 if($total < MAX_LIKERS)
1282                         $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1];
1283                 $str = implode(', ', $arr);
1284                 if($total >= MAX_LIKERS)
1285                         $str .= t(', and ') . $total - MAX_LIKERS . t(' other people');
1286                 $str .= (($type === 'like') ? t(' like this.') : t(' don\'t like this.'));
1287                 $o .= "\t" . '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>';
1288         }
1289         return $o;
1290 }}
1291
1292
1293 // wrapper to load a view template, checking for alternate
1294 // languages before falling back to the default
1295
1296 if(! function_exists('load_view_file')) {
1297 function load_view_file($s) {
1298         $b = basename($s);
1299         $d = dirname($s);
1300         $lang = get_config('system','language');
1301         if($lang === false)
1302                 $lang = 'en';
1303         if(file_exists("$d/$lang/$b"))
1304                 return file_get_contents("$d/$lang/$b");
1305         return file_get_contents($s);
1306 }}
1307
1308 // for html,xml parsing - let's say you've got
1309 // an attribute foobar="class1 class2 class3"
1310 // and you want to find out if it contains 'class3'.
1311 // you can't use a normal sub string search because you
1312 // might match 'notclass3' and a regex to do the job is 
1313 // possible but a bit complicated. 
1314 // pass the attribute string as $attr and the attribute you 
1315 // are looking for as $s - returns true if found, otherwise false
1316
1317 if(! function_exists('attribute_contains')) {
1318 function attribute_contains($attr,$s) {
1319         $a = explode(' ', $attr);
1320         if(count($a) && in_array($s,$a))
1321                 return true;
1322         return false;
1323 }}
1324
1325 if(! function_exists('logger')) {
1326 function logger($msg,$level = 0) {
1327
1328         $debugging = get_config('system','debugging');
1329         $loglevel  = intval(get_config('system','loglevel'));
1330         $logfile   = get_config('system','logfile');
1331
1332         if((! $debugging) || (! $logfile) || ($level > $loglevel))
1333                 return;
1334         
1335         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
1336         return;
1337 }}
1338
1339
1340 if(! function_exists('activity_match')) {
1341 function activity_match($haystack,$needle) {
1342         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
1343                 return true;
1344         return false;
1345 }}
1346
1347
1348 // Pull out all #hashtags and @person tags from $s;
1349 // We also get @person@domain.com - which would make 
1350 // the regex quite complicated as tags can also
1351 // end a sentence. So we'll run through our results
1352 // and strip the period from any tags which end with one.
1353 // Returns array of tags found, or empty array.
1354
1355
1356 if(! function_exists('get_tags')) {
1357 function get_tags($s) {
1358         $ret = array();
1359         if(preg_match_all('/([@#][^ ,:?]*)([ ,:?]|$)/',$s,$match)) {
1360                 foreach($match[1] as $match) {
1361                         if(substr($match,-1,1) === '.')
1362                                 $ret[] = substr($match,0,-1);
1363                         else
1364                                 $ret[] = $match;
1365                 }
1366         }
1367
1368         return $ret;
1369 }}
1370
1371
1372 // quick and dirty quoted_printable encoding
1373
1374 if(! function_exists('qp')) {
1375 function qp($s) {
1376 return str_replace ("%","=",rawurlencode($s));
1377 }} 
1378
1379
1380 if(! function_exists('like_puller')) {
1381 function like_puller($a,$item,&$arr,$mode) {
1382
1383         $url = '';
1384         $sparkle = '';
1385         $verb = (($mode === 'like') ? ACTIVITY_LIKE : ACTIVITY_DISLIKE);
1386
1387         if((activity_match($item['verb'],$verb)) && ($item['id'] != $item['parent'])) {
1388                 $url = $item['author-link'];
1389                 if(($item['network'] === 'dfrn') && (! $item['self']) && ($item['author-link'] == $item['url'])) {
1390                         $url = $a->get_baseurl() . '/redir/' . $item['contact-id'];
1391                         $sparkle = ' class="sparkle" ';
1392                 }
1393                 if(! ((isset($arr[$item['parent'] . '-l'])) && (is_array($arr[$item['parent'] . '-l']))))
1394                         $arr[$item['parent'] . '-l'] = array();
1395                 if(! isset($arr[$item['parent']]))
1396                         $arr[$item['parent']] = 1;
1397                 else    
1398                         $arr[$item['parent']] ++;
1399                 $arr[$item['parent'] . '-l'][] = '<a href="'. $url . '"'. $sparkle .'>' . $item['author-name'] . '</a>';
1400         }
1401         return;
1402 }}
1403
1404 if(! function_exists('get_mentions')) {
1405 function get_mentions($item) {
1406         $o = '';
1407         if(! strlen($item['tag']))
1408                 return $o;
1409
1410         $arr = explode(',',$item['tag']);
1411         foreach($arr as $x) {
1412                 $matches = null;
1413                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches))
1414                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
1415         }
1416         return $o;
1417 }}
1418
1419 if(! function_exists('contact_block')) {
1420 function contact_block() {
1421         $o = '';
1422         $a = get_app();
1423         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
1424                 return $o;
1425         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
1426                         intval($a->profile['uid'])
1427         );
1428         if(count($r)) {
1429                 $total = intval($r[0]['total']);
1430         }
1431         if(! $total) {
1432                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
1433                 return $o;
1434         }
1435         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT 24",
1436                         intval($a->profile['uid'])
1437         );
1438         if(count($r)) {
1439                 $o .= '<h4 class="contact-h4">' . $total . ' ' . t('Contacts') . '</h4><div id="contact-block">';
1440                 foreach($r as $rr) {
1441                         $redirect_url = $a->get_baseurl() . '/redir/' . $rr['id'];
1442                         if(local_user() && ($rr['uid'] == local_user())
1443                                 && ($rr['network'] === 'dfrn')) {
1444                                 $url = $redirect_url;
1445                                 $sparkle = ' sparkle';
1446                         }
1447                         else {
1448                                 $url = $rr['url'];
1449                                 $sparkle = '';
1450                         }
1451
1452                         $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";
1453                 }
1454                 $o .= '</div><div id="contact-block-end"></div>';
1455                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
1456                 
1457         }
1458         return $o;
1459
1460 }}
1461
1462 if(! function_exists('search')) {
1463 function search($s) {
1464         $a = get_app();
1465         $o  = '<div id="search-box">';
1466         $o .= '<form action="' . $a->get_baseurl() . '/search' . '" method="get" >';
1467         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
1468         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
1469         $o .= '</form></div>';
1470         return $o;
1471 }}
1472
1473 if(! function_exists('valid_email')) {
1474 function valid_email($x){
1475         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
1476                 return true;
1477         return false;
1478 }}
1479
1480
1481 if(! function_exists('gravatar_img')) {
1482 function gravatar_img($email) {
1483         $size = 175;
1484         $opt = 'identicon';   // psuedo-random geometric pattern if not found
1485         $rating = 'pg';
1486         $hash = md5(trim(strtolower($email)));
1487         
1488         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
1489                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
1490
1491         logger('gravatar: ' . $email . ' ' . $url);
1492         return $url;
1493 }}
1494