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