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