]> git.mxchange.org Git - friendica.git/blob - boot.php
warnings when no plugins installed
[friendica.git] / boot.php
1 <?php
2
3 set_time_limit(0);
4
5 define ( 'BUILD_ID',               1028   );
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 define ( 'DOWN_ARROW',             '&#x21e9;'       );
11          
12 /**
13  * log levels
14  */
15
16 define ( 'LOGGER_NORMAL',          0 );
17 define ( 'LOGGER_TRACE',           1 );
18 define ( 'LOGGER_DEBUG',           2 );
19 define ( 'LOGGER_DATA',            3 );
20 define ( 'LOGGER_ALL',             4 );
21
22 /**
23  * registration policies
24  */
25
26 define ( 'REGISTER_CLOSED',        0 );
27 define ( 'REGISTER_APPROVE',       1 );
28 define ( 'REGISTER_OPEN',          2 );
29
30 /**
31  * relationship types
32  */
33
34 define ( 'REL_VIP',        1);
35 define ( 'REL_FAN',        2);
36 define ( 'REL_BUD',        3);
37
38 /**
39  * Hook array order
40  */
41  
42 define ( 'HOOK_HOOK',      0);
43 define ( 'HOOK_FILE',      1);
44 define ( 'HOOK_FUNCTION',  2);
45
46 /**
47  *
48  * page/profile types
49  *
50  * PAGE_NORMAL is a typical personal profile account
51  * PAGE_SOAPBOX automatically approves all friend requests as REL_FAN, (readonly)
52  * PAGE_COMMUNITY automatically approves all friend requests as REL_FAN, but with 
53  *      write access to wall and comments (no email and not included in page owner's ACL lists)
54  * PAGE_FREELOVE automatically approves all friend requests as full friends (REL_BUD). 
55  *
56  */
57
58 define ( 'PAGE_NORMAL',            0 );
59 define ( 'PAGE_SOAPBOX',           1 );
60 define ( 'PAGE_COMMUNITY',         2 );
61 define ( 'PAGE_FREELOVE',          3 );
62
63 /**
64  * Maximum number of "people who like (or don't like) this"  that we will list by name
65  */
66
67 define ( 'MAX_LIKERS',    75);
68
69 /**
70  * email notification options
71  */
72
73 define ( 'NOTIFY_INTRO',   0x0001 );
74 define ( 'NOTIFY_CONFIRM', 0x0002 );
75 define ( 'NOTIFY_WALL',    0x0004 );
76 define ( 'NOTIFY_COMMENT', 0x0008 );
77 define ( 'NOTIFY_MAIL',    0x0010 );
78
79 /**
80  * various namespaces we may need to parse
81  */
82
83 define ( 'NAMESPACE_DFRN' ,           'http://purl.org/macgirvin/dfrn/1.0' ); 
84 define ( 'NAMESPACE_THREAD' ,         'http://purl.org/syndication/thread/1.0' );
85 define ( 'NAMESPACE_TOMB' ,           'http://purl.org/atompub/tombstones/1.0' );
86 define ( 'NAMESPACE_ACTIVITY',        'http://activitystrea.ms/spec/1.0/' );
87 define ( 'NAMESPACE_ACTIVITY_SCHEMA', 'http://activitystrea.ms/schema/1.0/' );
88 define ( 'NAMESPACE_MEDIA',           'http://purl.org/syndication/atommedia' );
89 define ( 'NAMESPACE_SALMON_ME',       'http://salmon-protocol.org/ns/magic-env' );
90 define ( 'NAMESPACE_OSTATUSSUB',      'http://ostatus.org/schema/1.0/subscribe' );
91 define ( 'NAMESPACE_GEORSS',          'http://www.georss.org/georss' );
92 define ( 'NAMESPACE_POCO',            'http://portablecontacts.net/spec/1.0' );
93 define ( 'NAMESPACE_FEED',            'http://schemas.google.com/g/2010#updates-from' );
94
95 /**
96  * activity stream defines
97  */
98
99 define ( 'ACTIVITY_LIKE',        NAMESPACE_ACTIVITY_SCHEMA . 'like' );
100 define ( 'ACTIVITY_DISLIKE',     NAMESPACE_DFRN            . '/dislike' );
101 define ( 'ACTIVITY_OBJ_HEART',   NAMESPACE_DFRN            . '/heart' );
102
103 define ( 'ACTIVITY_FRIEND',      NAMESPACE_ACTIVITY_SCHEMA . 'make-friend' );
104 define ( 'ACTIVITY_FOLLOW',      NAMESPACE_ACTIVITY_SCHEMA . 'follow' );
105 define ( 'ACTIVITY_UNFOLLOW',    NAMESPACE_ACTIVITY_SCHEMA . 'unfollow' );
106 define ( 'ACTIVITY_POST',        NAMESPACE_ACTIVITY_SCHEMA . 'post' );
107 define ( 'ACTIVITY_UPDATE',      NAMESPACE_ACTIVITY_SCHEMA . 'update' );
108 define ( 'ACTIVITY_TAG',         NAMESPACE_ACTIVITY_SCHEMA . 'tag' );
109
110 define ( 'ACTIVITY_OBJ_COMMENT', NAMESPACE_ACTIVITY_SCHEMA . 'comment' );
111 define ( 'ACTIVITY_OBJ_NOTE',    NAMESPACE_ACTIVITY_SCHEMA . 'note' );
112 define ( 'ACTIVITY_OBJ_PERSON',  NAMESPACE_ACTIVITY_SCHEMA . 'person' );
113 define ( 'ACTIVITY_OBJ_PHOTO',   NAMESPACE_ACTIVITY_SCHEMA . 'photo' );
114 define ( 'ACTIVITY_OBJ_P_PHOTO', NAMESPACE_ACTIVITY_SCHEMA . 'profile-photo' );
115 define ( 'ACTIVITY_OBJ_ALBUM',   NAMESPACE_ACTIVITY_SCHEMA . 'photo-album' );
116
117 /**
118  * item weight for query ordering
119  */
120
121 define ( 'GRAVITY_PARENT',       0);
122 define ( 'GRAVITY_LIKE',         3);
123 define ( 'GRAVITY_COMMENT',      6);
124
125 /**
126  *
127  * Reverse the effect of magic_quotes_gpc if it is enabled.
128  * Please disable magic_quotes_gpc so we don't have to do this.
129  * See http://php.net/manual/en/security.magicquotes.disabling.php
130  *
131  */
132
133 if (get_magic_quotes_gpc()) {
134     $process = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
135     while (list($key, $val) = each($process)) {
136         foreach ($val as $k => $v) {
137             unset($process[$key][$k]);
138             if (is_array($v)) {
139                 $process[$key][stripslashes($k)] = $v;
140                 $process[] = &$process[$key][stripslashes($k)];
141             } else {
142                 $process[$key][stripslashes($k)] = stripslashes($v);
143             }
144         }
145     }
146     unset($process);
147 }
148
149
150 /**
151  *
152  * class: App
153  *
154  * Our main application structure for the life of this page
155  * Primarily deals with the URL that got us here
156  * and tries to make some sense of it, and 
157  * stores our page contents and config storage
158  * and anything else that might need to be passed around 
159  * before we spit the page out. 
160  *
161  */
162
163 if(! class_exists('App')) {
164 class App {
165
166         public  $module_loaded = false;
167         public  $query_string;
168         public  $config;
169         public  $page;
170         public  $profile;
171         public  $user;
172         public  $cid;
173         public  $contact;
174         public  $content;
175         public  $data;
176         public  $error = false;
177         public  $cmd;
178         public  $argv;
179         public  $argc;
180         public  $module;
181         public  $pager;
182         public  $strings;   
183         public  $path;
184         public  $hooks;
185         public  $interactive = true;
186
187
188         private $scheme;
189         private $hostname;
190         private $baseurl;
191         private $db;
192
193         private $curl_code;
194         private $curl_headers;
195
196         function __construct() {
197
198                 $this->config = array();
199                 $this->page = array();
200                 $this->pager= array();
201
202                 $this->query_string = '';
203
204                 $this->scheme = ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']))      ?  'https' : 'http' );
205
206                 if(x($_SERVER,'SERVER_NAME'))
207                         $this->hostname = $_SERVER['SERVER_NAME'];
208
209                 set_include_path("include/$this->hostname" . PATH_SEPARATOR . 'include' . PATH_SEPARATOR . '.' );
210
211                 if((x($_SERVER,'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'],0,2) === "q=")
212                         $this->query_string = substr($_SERVER['QUERY_STRING'],2);
213                 if(x($_GET,'q'))
214                         $this->cmd = trim($_GET['q'],'/');
215
216                 /** 
217                  * Figure out if we are running at the top of a domain
218                  * or in a sub-directory and adjust accordingly
219                  */
220
221                 $path = trim(dirname($_SERVER['SCRIPT_NAME']),'/');
222                 if(isset($path) && strlen($path) && ($path != $this->path))
223                         $this->path = $path;
224
225
226                 /**
227                  *
228                  * Break the URL path into C style argc/argv style arguments for our
229                  * modules. Given "http://example.com/module/arg1/arg2", $this->argc
230                  * will be 3 (integer) and $this->argv will contain:
231                  *   [0] => 'module'
232                  *   [1] => 'arg1'
233                  *   [2] => 'arg2'
234                  *
235                  *
236                  * There will always be one argument. If provided a naked domain
237                  * URL, $this->argv[0] is set to "home".
238                  *
239                  */
240
241                 $this->argv = explode('/',$this->cmd);
242                 $this->argc = count($this->argv);
243                 if((array_key_exists('0',$this->argv)) && strlen($this->argv[0])) {
244                         $this->module = $this->argv[0];
245                 }
246                 else {
247                         $this->module = 'home';
248                 }
249
250                 /**
251                  * Special handling for the webfinger/lrdd host XRD file
252                  * Just spit out the contents and exit.
253                  */
254
255                 if($this->cmd === '.well-known/host-meta')
256                         require_once('include/hostxrd.php');
257
258
259                 /**
260                  * See if there is any page number information, and initialise 
261                  * pagination
262                  */
263
264                 $this->pager['page'] = ((x($_GET,'page')) ? $_GET['page'] : 1);
265                 $this->pager['itemspage'] = 50;
266                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
267                 $this->pager['total'] = 0;
268         }
269
270         function get_baseurl($ssl = false) {
271                 if(strlen($this->baseurl))
272                         return $this->baseurl;
273
274                 $this->baseurl = (($ssl) ? 'https' : $this->scheme) . "://" . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );
275                 return $this->baseurl;
276         }
277
278         function set_baseurl($url) {
279                 $this->baseurl = $url;
280                 $this->hostname = basename($url);
281         }
282
283         function get_hostname() {
284                 return $this->hostname;
285         }
286
287         function set_hostname($h) {
288                 $this->hostname = $h;
289         }
290
291         function set_path($p) {
292                 $this->path = trim(trim($p),'/');
293         } 
294
295         function get_path() {
296                 return $this->path;
297         }
298
299         function set_pager_total($n) {
300                 $this->pager['total'] = intval($n);
301         }
302
303         function set_pager_itemspage($n) {
304                 $this->pager['itemspage'] = intval($n);
305                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
306
307         } 
308
309         function init_pagehead() {
310                 $tpl = load_view_file("view/head.tpl");
311                 $this->page['htmlhead'] = replace_macros($tpl,array(
312                         '$baseurl' => $this->get_baseurl() . '/'
313                 ));
314         }
315
316         function set_curl_code($code) {
317                 $this->curl_code = $code;
318         }
319
320         function get_curl_code() {
321                 return $this->curl_code;
322         }
323
324         function set_curl_headers($headers) {
325                 $this->curl_headers = $headers;
326         }
327
328         function get_curl_headers() {
329                 return $this->curl_headers;
330         }
331
332
333 }}
334
335 // retrieve the App structure
336 // useful in functions which require it but don't get it passed to them
337
338 if(! function_exists('get_app')) {
339 function get_app() {
340         global $a;
341         return $a;
342 }};
343
344
345 // Multi-purpose function to check variable state.
346 // Usage: x($var) or $x($array,'key')
347 // returns false if variable/key is not set
348 // if variable is set, returns 1 if has 'non-zero' value, otherwise returns 0.
349 // e.g. x('') or x(0) returns 0;
350
351 if(! function_exists('x')) {
352 function x($s,$k = NULL) {
353         if($k != NULL) {
354                 if((is_array($s)) && (array_key_exists($k,$s))) {
355                         if($s[$k])
356                                 return (int) 1;
357                         return (int) 0;
358                 }
359                 return false;
360         }
361         else {          
362                 if(isset($s)) {
363                         if($s) {
364                                 return (int) 1;
365                         }
366                         return (int) 0;
367                 }
368                 return false;
369         }
370 }}
371
372 // called from db initialisation if db is dead.
373
374 if(! function_exists('system_unavailable')) {
375 function system_unavailable() {
376         include('system_unavailable.php');
377         killme();
378 }}
379
380 // Primarily involved with database upgrade, but also sets the 
381 // base url for use in cmdline programs which don't have
382 // $_SERVER variables, and synchronising the state of installed plugins.
383
384
385 if(! function_exists('check_config')) {
386 function check_config(&$a) {
387
388         load_config('system');
389
390         $build = get_config('system','build');
391         if(! x($build))
392                 $build = set_config('system','build',BUILD_ID);
393
394         $url = get_config('system','url');
395         if(! x($url))
396                 $url = set_config('system','url',$a->get_baseurl());
397
398         if($build != BUILD_ID) {
399                 $stored = intval($build);
400                 $current = intval(BUILD_ID);
401                 if(($stored < $current) && file_exists('update.php')) {
402                         // We're reporting a different version than what is currently installed.
403                         // Run any existing update scripts to bring the database up to current.
404
405                         require_once('update.php');
406                         for($x = $stored; $x < $current; $x ++) {
407                                 if(function_exists('update_' . $x)) {
408                                         $func = 'update_' . $x;
409                                         $func($a);
410                                 }
411                         }
412                         set_config('system','build', BUILD_ID);
413                 }
414         }
415
416         /**
417          *
418          * Synchronise plugins:
419          *
420          * $a->config['system']['addon'] contains a comma-separated list of names
421          * of plugins/addons which are used on this system. 
422          * Go through the database list of already installed addons, and if we have
423          * an entry, but it isn't in the config list, call the uninstall procedure
424          * and mark it uninstalled in the database (for now we'll remove it).
425          * Then go through the config list and if we have a plugin that isn't installed,
426          * call the install procedure and add it to the database.
427          *
428          */
429
430         $r = q("SELECT * FROM `addon` WHERE `installed` = 1");
431         if(count($r))
432                 $installed = $r;
433         else
434                 $installed = array();
435
436         $plugins = get_config('system','addon');
437         $plugins_arr = array();
438
439         if($plugins)
440                 $plugins_arr = explode(',',str_replace(' ', '',$plugins));
441
442         $installed_arr = array();
443
444         if(count($installed)) {
445                 foreach($installed as $i) {
446                         if(! in_array($i['name'],$plugins_arr)) {
447                                 logger("Addons: uninstalling " . $i['name']);
448                                 q("DELETE FROM `addon` WHERE `id` = %d LIMIT 1",
449                                         intval($i['id'])
450                                 );
451
452                                 @include_once('addon/' . $i['name'] . '/' . $i['name'] . '.php');
453                                 if(function_exists($i['name'] . '_uninstall')) {
454                                         $func = $i['name'] . '_uninstall';
455                                         $func();
456                                 }
457                         }
458                         else
459                                 $installed_arr[] = $i['name'];
460                 }
461         }
462
463         if(count($plugins_arr)) {
464                 foreach($plugins_arr as $p) {
465                         if(! in_array($p,$installed_arr)) {
466                                 logger("Addons: installing " . $p);
467                                 @include_once('addon/' . $p . '/' . $p . '.php');
468                                 if(function_exists($p . '_install')) {
469                                         $func = $p . '_install';
470                                         $func();
471                                         $r = q("INSERT INTO `addon` (`name`, `installed`) VALUES ( '%s', 1 ) ",
472                                                 dbesc($p)
473                                         );
474                                 }
475                         }
476                 }
477         }
478
479         return;
480 }}
481
482
483 // This is our template processor.
484 // $s is the string requiring macro substitution.
485 // $r is an array of key value pairs (search => replace)
486 // returns substituted string.
487 // WARNING: this is pretty basic, and doesn't properly handle search strings that are substrings of each other.
488 // For instance if 'test' => "foo" and 'testing' => "bar", testing could become either bar or fooing, 
489 // depending on the order in which they were declared in the array.   
490
491 if(! function_exists('replace_macros')) {  
492 function replace_macros($s,$r) {
493
494         $search = array();
495         $replace = array();
496
497         if(is_array($r) && count($r)) {
498                 foreach ($r as $k => $v ) {
499                         $search[] =  $k;
500                         $replace[] = $v;
501                 }
502         }
503         return str_replace($search,$replace,$s);
504 }}
505
506
507 // load string translation table for alternate language
508
509 if(! function_exists('load_translation_table')) {
510 function load_translation_table($lang) {
511         global $a;
512
513         if(file_exists("view/$lang/strings.php"))
514                 include("view/$lang/strings.php");
515 }}
516
517 // translate string if translation exists
518
519 if(! function_exists('t')) {
520 function t($s) {
521
522         $a = get_app();
523
524         if(x($a->strings,$s))
525                 return $a->strings[$s];
526         return $s;
527 }}
528
529 // curl wrapper. If binary flag is true, return binary
530 // results. 
531
532 if(! function_exists('fetch_url')) {
533 function fetch_url($url,$binary = false, &$redirects = 0) {
534
535         $a = get_app();
536
537         $ch = curl_init($url);
538         if(($redirects > 8) || (! $ch)) 
539                 return false;
540
541         curl_setopt($ch, CURLOPT_HEADER, true);
542         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
543
544
545         $curl_time = intval(get_config('system','curl_timeout'));
546         curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
547
548         // by default we will allow self-signed certs
549         // but you can override this
550
551         $check_cert = get_config('system','verifyssl');
552         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
553
554         $prx = get_config('system','proxy');
555         if(strlen($prx)) {
556                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
557                 curl_setopt($ch, CURLOPT_PROXY, $prx);
558                 $prxusr = get_config('system','proxyuser');
559                 if(strlen($prxusr))
560                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
561         }
562         if($binary)
563                 curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
564
565         $a->set_curl_code(0);
566
567         // don't let curl abort the entire application
568         // if it throws any errors.
569
570         $s = @curl_exec($ch);
571
572         $http_code = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
573         $header = substr($s,0,strpos($s,"\r\n\r\n"));
574         if(stristr($header,'100') && (strlen($header) < 30)) {
575                 // 100 Continue has two headers, get the real one
576                 $s = substr($s,strlen($header)+4);
577                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
578         }
579         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
580         $matches = array();
581         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
582         $url = trim(array_pop($matches));
583         $url_parsed = parse_url($url);
584         if (isset($url_parsed)) {
585             $redirects++;
586             return fetch_url($url,$binary,$redirects);
587         }
588     }
589         $a->set_curl_code($http_code);
590         $body = substr($s,strlen($header)+4);
591         $a->set_curl_headers($header);
592
593         curl_close($ch);
594         return($body);
595 }}
596
597 // post request to $url. $params is an array of post variables.
598
599 if(! function_exists('post_url')) {
600 function post_url($url,$params, $headers = null, &$redirects = 0) {
601         $a = get_app();
602         $ch = curl_init($url);
603         if(($redirects > 8) || (! $ch)) 
604                 return false;
605
606         curl_setopt($ch, CURLOPT_HEADER, true);
607         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
608         curl_setopt($ch, CURLOPT_POST,1);
609         curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
610
611         $curl_time = intval(get_config('system','curl_timeout'));
612         curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
613
614         if(is_array($headers))
615                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
616
617         $check_cert = get_config('system','verifyssl');
618         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
619         $prx = get_config('system','proxy');
620         if(strlen($prx)) {
621                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
622                 curl_setopt($ch, CURLOPT_PROXY, $prx);
623                 $prxusr = get_config('system','proxyuser');
624                 if(strlen($prxusr))
625                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
626         }
627
628         $a->set_curl_code(0);
629
630         // don't let curl abort the entire application
631         // if it throws any errors.
632
633         $s = @curl_exec($ch);
634
635         $http_code = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
636         $header = substr($s,0,strpos($s,"\r\n\r\n"));
637         if(stristr($header,'100') && (strlen($header) < 30)) {
638                 // 100 Continue has two headers, get the real one
639                 $s = substr($s,strlen($header)+4);
640                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
641         }
642         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
643         $matches = array();
644         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
645         $url = trim(array_pop($matches));
646         $url_parsed = parse_url($url);
647         if (isset($url_parsed)) {
648             $redirects++;
649             return post_url($url,$binary,$headers,$redirects);
650         }
651     }
652         $a->set_curl_code($http_code);
653         $body = substr($s,strlen($header)+4);
654         $a->set_curl_headers($header);
655
656         curl_close($ch);
657         return($body);
658 }}
659
660 // random hash, 64 chars
661
662 if(! function_exists('random_string')) {
663 function random_string() {
664         return(hash('sha256',uniqid(rand(),true)));
665 }}
666
667 /**
668  * This is our primary input filter. 
669  *
670  * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
671  * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
672  * after cleansing, and angle chars with the high bit set could get through as markup.
673  * 
674  * This is now disabled because it was interfering with some legitimate unicode sequences 
675  * and hopefully there aren't a lot of those browsers left. 
676  *
677  * Use this on any text input where angle chars are not valid or permitted
678  * They will be replaced with safer brackets. This may be filtered further
679  * if these are not allowed either.   
680  *
681  */
682
683 if(! function_exists('notags')) {
684 function notags($string) {
685
686         return(str_replace(array("<",">"), array('[',']'), $string));
687
688 //  High-bit filter no longer used
689 //      return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
690 }}
691
692 // use this on "body" or "content" input where angle chars shouldn't be removed,
693 // and allow them to be safely displayed.
694
695 if(! function_exists('escape_tags')) {
696 function escape_tags($string) {
697
698         return(htmlspecialchars($string));
699 }}
700
701 // wrapper for adding a login box. If $register == true provide a registration
702 // link. This will most always depend on the value of $a->config['register_policy'].
703 // returns the complete html for inserting into the page
704
705 if(! function_exists('login')) {
706 function login($register = false) {
707         $o = "";
708         $register_html = (($register) ? load_view_file("view/register-link.tpl") : "");
709
710         $noid = get_config('system','no_openid');
711         if($noid) {
712                 $classname = 'no-openid';
713                 $namelabel = t('Nickname or Email address: ');
714                 $passlabel = t('Password: ');
715                 $login     = t('Login');
716         }
717         else {
718                 $classname = 'openid';
719                 $namelabel = t('Nickname/Email/OpenID: ');
720                 $passlabel = t("Password \x28if not OpenID\x29: ");
721                 $login     = t('Login');
722         }
723         $lostpass = t('Forgot your password?');
724         $lostlink = t('Password Reset');
725
726         if(x($_SESSION,'authenticated')) {
727                 $tpl = load_view_file("view/logout.tpl");
728         }
729         else {
730                 $tpl = load_view_file("view/login.tpl");
731
732         }
733         
734         $o = replace_macros($tpl,array(
735                 '$register_html' => $register_html, 
736                 '$classname' => $classname,
737                 '$namelabel' => $namelabel,
738                 '$passlabel' => $passlabel,
739                 '$login' => $login,
740                 '$lostpass' => $lostpass,
741                 '$lostlink' => $lostlink 
742         ));
743
744         return $o;
745 }}
746
747 // generate a string that's random, but usually pronounceable. 
748 // used to generate initial passwords
749
750 if(! function_exists('autoname')) {
751 function autoname($len) {
752
753         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u'); 
754         if(mt_rand(0,5) == 4)
755                 $vowels[] = 'y';
756
757         $cons = array(
758                         'b','bl','br',
759                         'c','ch','cl','cr',
760                         'd','dr',
761                         'f','fl','fr',
762                         'g','gh','gl','gr',
763                         'h',
764                         'j',
765                         'k','kh','kl','kr',
766                         'l',
767                         'm',
768                         'n',
769                         'p','ph','pl','pr',
770                         'qu',
771                         'r','rh',
772                         's','sc','sh','sm','sp','st',
773                         't','th','tr',
774                         'v',
775                         'w','wh',
776                         'x',
777                         'z','zh'
778                         );
779
780         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
781                                 'nd','ng','nk','nt','rn','rp','rt');
782
783         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
784                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
785
786         $start = mt_rand(0,2);
787         if($start == 0)
788                 $table = $vowels;
789         else
790                 $table = $cons;
791
792         $word = '';
793
794         for ($x = 0; $x < $len; $x ++) {
795                 $r = mt_rand(0,count($table) - 1);
796                 $word .= $table[$r];
797   
798                 if($table == $vowels)
799                         $table = array_merge($cons,$midcons);
800                 else
801                         $table = $vowels;
802
803         }
804
805         $word = substr($word,0,$len);
806
807         foreach($noend as $noe) {
808                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
809                         $word = substr($word,0,-1);
810                         break;
811                 }
812         }
813         if(substr($word,-1) == 'q')
814                 $word = substr($word,0,-1);    
815         return $word;
816 }}
817
818 // Used to end the current process, after saving session state. 
819
820 if(! function_exists('killme')) {
821 function killme() {
822         session_write_close();
823         exit;
824 }}
825
826 // redirect to another URL and terminate this process.
827
828 if(! function_exists('goaway')) {
829 function goaway($s) {
830         header("Location: $s");
831         killme();
832 }}
833
834 // Generic XML return
835 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable 
836 // of $st and an optional text <message> of $message and terminates the current process. 
837
838 if(! function_exists('xml_status')) {
839 function xml_status($st, $message = '') {
840
841         $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
842
843         if($st)
844                 logger('xml_status returning non_zero: ' . $st . " message=" . $message);
845
846         header( "Content-type: text/xml" );
847         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
848         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
849         killme();
850 }}
851
852 // Returns the uid of locally logged in user or false.
853
854 if(! function_exists('local_user')) {
855 function local_user() {
856         if((x($_SESSION,'authenticated')) && (x($_SESSION,'uid')))
857                 return intval($_SESSION['uid']);
858         return false;
859 }}
860
861 // Returns contact id of authenticated site visitor or false
862
863 if(! function_exists('remote_user')) {
864 function remote_user() {
865         if((x($_SESSION,'authenticated')) && (x($_SESSION,'visitor_id')))
866                 return intval($_SESSION['visitor_id']);
867         return false;
868 }}
869
870 // contents of $s are displayed prominently on the page the next time
871 // a page is loaded. Usually used for errors or alerts.
872
873 if(! function_exists('notice')) {
874 function notice($s) {
875         $a = get_app();
876         if($a->interactive)
877                 $_SESSION['sysmsg'] .= $s;
878 }}
879
880 // wrapper around config to limit the text length of an incoming message
881
882 if(! function_exists('get_max_import_size')) {
883 function get_max_import_size() {
884         global $a;
885         return ((x($a->config,'max_import_size')) ? $a->config['max_import_size'] : 0 );
886 }}
887
888
889 // escape text ($str) for XML transport
890 // returns escaped text.
891
892 if(! function_exists('xmlify')) {
893 function xmlify($str) {
894         $buffer = '';
895         
896         for($x = 0; $x < strlen($str); $x ++) {
897                 $char = $str[$x];
898         
899                 switch( $char ) {
900
901                         case "\r" :
902                                 break;
903                         case "&" :
904                                 $buffer .= '&amp;';
905                                 break;
906                         case "'" :
907                                 $buffer .= '&apos;';
908                                 break;
909
910                         case "\"" :
911                                 $buffer .= '&quot;';
912                                 break;
913                         case '<' :
914                                 $buffer .= '&lt;';
915                                 break;
916                         case '>' :
917                                 $buffer .= '&gt;';
918                                 break;
919                         case "\n" :
920                                 $buffer .= "\n";
921                                 break;
922                         default :
923                                 $buffer .= $char;
924                                 break;
925                 }       
926         }
927         $buffer = trim($buffer);
928         return($buffer);
929 }}
930
931 // undo an xmlify
932 // pass xml escaped text ($s), returns unescaped text
933
934 if(! function_exists('unxmlify')) {
935 function unxmlify($s) {
936         $ret = str_replace('&amp;','&', $s);
937         $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
938         return $ret;    
939 }}
940
941 // convenience wrapper, reverse the operation "bin2hex"
942
943 if(! function_exists('hex2bin')) {
944 function hex2bin($s) {
945         return(pack("H*",$s));
946 }}
947
948 // Automatic pagination.
949 // To use, get the count of total items.
950 // Then call $a->set_pager_total($number_items);
951 // Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
952 // Then call paginate($a) after the end of the display loop to insert the pager block on the page
953 // (assuming there are enough items to paginate).
954 // When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
955 // will limit the results to the correct items for the current page. 
956 // The actual page handling is then accomplished at the application layer. 
957
958 if(! function_exists('paginate')) {
959 function paginate(&$a) {
960         $o = '';
961         $stripped = preg_replace('/(&page=[0-9]*)/','',$a->query_string);
962         $stripped = str_replace('q=','',$stripped);
963         $stripped = trim($stripped,'/');
964         $url = $a->get_baseurl() . '/' . $stripped;
965
966
967           if($a->pager['total'] > $a->pager['itemspage']) {
968                 $o .= '<div class="pager">';
969                 if($a->pager['page'] != 1)
970                         $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
971
972                 $o .=  "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
973
974                 $numpages = $a->pager['total'] / $a->pager['itemspage'];
975
976                 $numstart = 1;
977                 $numstop = $numpages;
978
979                 if($numpages > 14) {
980                         $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
981                         $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
982                 }
983    
984                 for($i = $numstart; $i <= $numstop; $i++){
985                         if($i == $a->pager['page'])
986                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
987                         else
988                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
989                         $o .= '</span> ';
990                 }
991
992                 if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
993                         if($i == $a->pager['page'])
994                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
995                         else
996                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
997                         $o .= '</span> ';
998                 }
999
1000                 $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
1001                 $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
1002
1003                 if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
1004                         $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
1005                 $o .= '</div>'."\r\n";
1006         }
1007         return $o;
1008 }}
1009
1010 // Turn user/group ACLs stored as angle bracketed text into arrays
1011
1012 if(! function_exists('expand_acl')) {
1013 function expand_acl($s) {
1014         // turn string array of angle-bracketed elements into numeric array
1015         // e.g. "<1><2><3>" => array(1,2,3);
1016         $ret = array();
1017
1018         if(strlen($s)) {
1019                 $t = str_replace('<','',$s);
1020                 $a = explode('>',$t);
1021                 foreach($a as $aa) {
1022                         if(intval($aa))
1023                                 $ret[] = intval($aa);
1024                 }
1025         }
1026         return $ret;
1027 }}              
1028
1029 // Used to wrap ACL elements in angle brackets for storage 
1030
1031 if(! function_exists('sanitise_acl')) {
1032 function sanitise_acl(&$item) {
1033         if(intval($item))
1034                 $item = '<' . intval(notags(trim($item))) . '>';
1035         else
1036                 unset($item);
1037 }}
1038
1039 // retrieve a "family" of config variables from database to cached storage
1040
1041 if(! function_exists('load_config')) {
1042 function load_config($family) {
1043         global $a;
1044         $r = q("SELECT * FROM `config` WHERE `cat` = '%s'",
1045                 dbesc($family)
1046         );
1047         if(count($r)) {
1048                 foreach($r as $rr) {
1049                         $k = $rr['k'];
1050                         $a->config[$family][$k] = $rr['v'];
1051                 }
1052         }
1053 }}
1054
1055 // get a particular config variable given the family name
1056 // and key. Returns false if not set.
1057 // $instore is only used by the set_config function
1058 // to determine if the key already exists in the DB
1059 // If a key is found in the DB but doesn't exist in
1060 // local config cache, pull it into the cache so we don't have
1061 // to hit the DB again for this item.
1062
1063 if(! function_exists('get_config')) {
1064 function get_config($family, $key, $instore = false) {
1065
1066         global $a;
1067
1068         if(! $instore) {
1069                 if(isset($a->config[$family][$key])) {
1070                         if($a->config[$family][$key] === '!<unset>!') {
1071                                 return false;
1072                         }
1073                         return $a->config[$family][$key];
1074                 }
1075         }
1076         $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1077                 dbesc($family),
1078                 dbesc($key)
1079         );
1080         if(count($ret)) {
1081                 $a->config[$family][$key] = $ret[0]['v'];
1082                 return $ret[0]['v'];
1083         }
1084         else {
1085                 $a->config[$family][$key] = '!<unset>!';
1086         }
1087         return false;
1088 }}
1089
1090 // Store a config value ($value) in the category ($family)
1091 // under the key ($key)
1092 // Return the value, or false if the database update failed
1093
1094 if(! function_exists('set_config')) {
1095 function set_config($family,$key,$value) {
1096
1097         global $a;
1098         $a->config[$family][$key] = $value;
1099
1100         if(get_config($family,$key,true) === false) {
1101                 $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
1102                         dbesc($family),
1103                         dbesc($key),
1104                         dbesc($value)
1105                 );
1106                 if($ret) 
1107                         return $value;
1108                 return $ret;
1109         }
1110         $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1111                 dbesc($value),
1112                 dbesc($family),
1113                 dbesc($key)
1114         );
1115         if($ret)
1116                 return $value;
1117         return $ret;
1118 }}
1119
1120
1121 if(! function_exists('get_pconfig')) {
1122 function get_pconfig($uid,$family, $key, $instore = false) {
1123
1124         global $a;
1125
1126         if(! $instore) {
1127                 if(isset($a->config[$uid][$family][$key])) {
1128                         if($a->config[$uid][$family][$key] === '!<unset>!') {
1129                                 return false;
1130                         }
1131                         return $a->config[$uid][$family][$key];
1132                 }
1133         }
1134         $ret = q("SELECT `v` FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1135                 intval($uid),
1136                 dbesc($family),
1137                 dbesc($key)
1138         );
1139         if(count($ret)) {
1140                 $a->config[$uid][$family][$key] = $ret[0]['v'];
1141                 return $ret[0]['v'];
1142         }
1143         else {
1144                 $a->config[$uid][$family][$key] = '!<unset>!';
1145         }
1146         return false;
1147 }}
1148
1149 if(! function_exists('del_config')) {
1150 function del_config($family,$key) {
1151
1152         global $a;
1153         if(x($a->config[$family],$key))
1154                 unset($a->config[$family][$key]);
1155         $ret = q("DELETE FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1156                 dbesc($cat),
1157                 dbesc($key)
1158         );
1159         return $ret;
1160 }}
1161
1162
1163
1164 // Same as above functions except these are for personal config storage and take an
1165 // additional $uid argument.
1166
1167 if(! function_exists('set_pconfig')) {
1168 function set_pconfig($uid,$family,$key,$value) {
1169
1170         global $a;
1171         $a->config[$uid][$family][$key] = $value;
1172
1173         if(get_pconfig($uid,$family,$key,true) === false) {
1174                 $ret = q("INSERT INTO `pconfig` ( `uid`, `cat`, `k`, `v` ) VALUES ( %d, '%s', '%s', '%s' ) ",
1175                         intval($uid),
1176                         dbesc($family),
1177                         dbesc($key),
1178                         dbesc($value)
1179                 );
1180                 if($ret) 
1181                         return $value;
1182                 return $ret;
1183         }
1184         $ret = q("UPDATE `pconfig` SET `v` = '%s' WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1185                 intval($uid),
1186                 dbesc($value),
1187                 dbesc($family),
1188                 dbesc($key)
1189         );
1190         if($ret)
1191                 return $value;
1192         return $ret;
1193 }}
1194
1195 if(! function_exists('del_pconfig')) {
1196 function del_pconfig($uid,$family,$key) {
1197
1198         global $a;
1199         if(x($a->config[$uid][$family],$key))
1200                 unset($a->config[$uid][$family][$key]);
1201         $ret = q("DELETE FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1202                 intval($uid),
1203                 dbesc($cat),
1204                 dbesc($key)
1205         );
1206         return $ret;
1207 }}
1208
1209
1210 // convert an XML document to a normalised, case-corrected array
1211 // used by webfinger
1212
1213 if(! function_exists('convert_xml_element_to_array')) {
1214 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
1215
1216         // If we're getting too deep, bail out
1217         if ($recursion_depth > 512) {
1218                 return(null);
1219         }
1220
1221         if (!is_string($xml_element) &&
1222         !is_array($xml_element) &&
1223         (get_class($xml_element) == 'SimpleXMLElement')) {
1224                 $xml_element_copy = $xml_element;
1225                 $xml_element = get_object_vars($xml_element);
1226         }
1227
1228         if (is_array($xml_element)) {
1229                 $result_array = array();
1230                 if (count($xml_element) <= 0) {
1231                         return (trim(strval($xml_element_copy)));
1232                 }
1233
1234                 foreach($xml_element as $key=>$value) {
1235
1236                         $recursion_depth++;
1237                         $result_array[strtolower($key)] =
1238                 convert_xml_element_to_array($value, $recursion_depth);
1239                         $recursion_depth--;
1240                 }
1241                 if ($recursion_depth == 0) {
1242                         $temp_array = $result_array;
1243                         $result_array = array(
1244                                 strtolower($xml_element_copy->getName()) => $temp_array,
1245                         );
1246                 }
1247
1248                 return ($result_array);
1249
1250         } else {
1251                 return (trim(strval($xml_element)));
1252         }
1253 }}
1254
1255 // Given an email style address, perform webfinger lookup and 
1256 // return the resulting DFRN profile URL, or if no DFRN profile URL
1257 // is located, returns an OStatus subscription template (prefixed 
1258 // with the string 'stat:' to identify it as on OStatus template).
1259 // If this isn't an email style address just return $s.
1260 // Return an empty string if email-style addresses but webfinger fails,
1261 // or if the resultant personal XRD doesn't contain a supported 
1262 // subscription/friend-request attribute.
1263
1264 if(! function_exists('webfinger_dfrn')) {
1265 function webfinger_dfrn($s) {
1266         if(! strstr($s,'@')) {
1267                 return $s;
1268         }
1269         $links = webfinger($s);
1270         logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
1271         if(count($links)) {
1272                 foreach($links as $link)
1273                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
1274                                 return $link['@attributes']['href'];
1275                 foreach($links as $link)
1276                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
1277                                 return 'stat:' . $link['@attributes']['template'];              
1278         }
1279         return '';
1280 }}
1281
1282 // Given an email style address, perform webfinger lookup and 
1283 // return the array of link attributes from the personal XRD file.
1284 // On error/failure return an empty array.
1285
1286
1287 if(! function_exists('webfinger')) {
1288 function webfinger($s) {
1289         $host = '';
1290         if(strstr($s,'@')) {
1291                 $host = substr($s,strpos($s,'@') + 1);
1292         }
1293         if(strlen($host)) {
1294                 $tpl = fetch_lrdd_template($host);
1295                 logger('webfinger: lrdd template: ' . $tpl);
1296                 if(strlen($tpl)) {
1297                         $pxrd = str_replace('{uri}', urlencode('acct:'.$s), $tpl);
1298                         $links = fetch_xrd_links($pxrd);
1299                         if(! count($links)) {
1300                                 // try with double slashes
1301                                 $pxrd = str_replace('{uri}', urlencode('acct://'.$s), $tpl);
1302                                 $links = fetch_xrd_links($pxrd);
1303                         }
1304                         return $links;
1305                 }
1306         }
1307         return array();
1308 }}
1309
1310 if(! function_exists('lrdd')) {
1311 function lrdd($uri) {
1312
1313         $a = get_app();
1314
1315         if(strstr($uri,'@')) {  
1316                 return(webfinger($uri));
1317         }
1318         else {
1319                 $html = fetch_url($uri);
1320                 $headers = $a->get_curl_headers();
1321                 $lines = explode("\n",$headers);
1322                 if(count($lines)) {
1323                         foreach($lines as $line) {                              
1324                                 // TODO alter the following regex to support multiple relations (space separated)
1325                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1326                                         $link = $matches[1];
1327                                         break;
1328                                 }
1329                         }
1330                 }
1331                 if(! isset($link)) {
1332                         // parse the page of the supplied URL looking for rel links
1333
1334                         require_once('library/HTML5/Parser.php');
1335                         $dom = HTML5_Parser::parse($html);
1336
1337                         if($dom) {
1338                                 $items = $dom->getElementsByTagName('link');
1339
1340                                 foreach($items as $item) {
1341                                         $x = $item->getAttribute('rel');
1342                                         if($x == "lrdd") {
1343                                                 $link = $item->getAttribute('href');
1344                                                 break;
1345                                         }
1346                                 }
1347                         }
1348                 }
1349
1350                 if(isset($link))
1351                         return(fetch_xrd_links($link));
1352         }
1353         return array();
1354 }}
1355
1356
1357
1358 // Given a host name, locate the LRDD template from that
1359 // host. Returns the LRDD template or an empty string on
1360 // error/failure.
1361
1362 if(! function_exists('fetch_lrdd_template')) {
1363 function fetch_lrdd_template($host) {
1364         $tpl = '';
1365         $url = 'http://' . $host . '/.well-known/host-meta' ;
1366         $links = fetch_xrd_links($url);
1367         if(count($links)) {
1368                 foreach($links as $link)
1369                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
1370                                 $tpl = $link['@attributes']['template'];
1371         }
1372         if(! strpos($tpl,'{uri}'))
1373                 $tpl = '';
1374         return $tpl;
1375 }}
1376
1377 // Given a URL, retrieve the page as an XRD document.
1378 // Return an array of links.
1379 // on error/failure return empty array.
1380
1381 if(! function_exists('fetch_xrd_links')) {
1382 function fetch_xrd_links($url) {
1383
1384
1385         $xml = fetch_url($url);
1386         if (! $xml)
1387                 return array();
1388
1389         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
1390         $h = simplexml_load_string($xml);
1391         $arr = convert_xml_element_to_array($h);
1392
1393         if(isset($arr['xrd']['link'])) {
1394                 $link = $arr['xrd']['link'];
1395                 if(! isset($link[0]))
1396                         $links = array($link);
1397                 else
1398                         $links = $link;
1399                 return $links;
1400         }
1401         return array();
1402 }}
1403
1404 // Convert an ACL array to a storable string
1405
1406 if(! function_exists('perms2str')) {
1407 function perms2str($p) {
1408         $ret = '';
1409         $tmp = $p;
1410         if(is_array($tmp)) {
1411                 array_walk($tmp,'sanitise_acl');
1412                 $ret = implode('',$tmp);
1413         }
1414         return $ret;
1415 }}
1416
1417 // generate a guaranteed unique (for this domain) item ID for ATOM
1418 // safe from birthday paradox
1419
1420 if(! function_exists('item_new_uri')) {
1421 function item_new_uri($hostname,$uid) {
1422
1423         do {
1424                 $dups = false;
1425                 $hash = random_string();
1426
1427                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
1428
1429                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
1430                         dbesc($uri));
1431                 if(count($r))
1432                         $dups = true;
1433         } while($dups == true);
1434         return $uri;
1435 }}
1436
1437 // Generate a guaranteed unique photo ID.
1438 // safe from birthday paradox
1439
1440 if(! function_exists('photo_new_resource')) {
1441 function photo_new_resource() {
1442
1443         do {
1444                 $found = false;
1445                 $resource = hash('md5',uniqid(mt_rand(),true));
1446                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1447                         dbesc($resource)
1448                 );
1449                 if(count($r))
1450                         $found = true;
1451         } while($found == true);
1452         return $resource;
1453 }}
1454
1455
1456 // Take a URL from the wild, prepend http:// if necessary
1457 // and check DNS to see if it's real
1458 // return true if it's OK, false if something is wrong with it
1459
1460 if(! function_exists('validate_url')) {
1461 function validate_url(&$url) {
1462         if(substr($url,0,4) != 'http')
1463                 $url = 'http://' . $url;
1464         $h = parse_url($url);
1465
1466         if(($h) && (checkdnsrr($h['host'], 'ANY'))) {
1467                 return true;
1468         }
1469         return false;
1470 }}
1471
1472 // checks that email is an actual resolvable internet address
1473
1474 if(! function_exists('validate_email')) {
1475 function validate_email($addr) {
1476
1477         if(! strpos($addr,'@'))
1478                 return false;
1479         $h = substr($addr,strpos($addr,'@') + 1);
1480
1481         if(($h) && (checkdnsrr($h, 'ANY'))) {
1482                 return true;
1483         }
1484         return false;
1485 }}
1486
1487 // Check $url against our list of allowed sites,
1488 // wildcards allowed. If allowed_sites is unset return true;
1489 // If url is allowed, return true.
1490 // otherwise, return false
1491
1492 if(! function_exists('allowed_url')) {
1493 function allowed_url($url) {
1494
1495         $h = parse_url($url);
1496
1497         if(! $h) {
1498                 return false;
1499         }
1500
1501         $str_allowed = get_config('system','allowed_sites');
1502         if(! $str_allowed)
1503                 return true;
1504
1505         $found = false;
1506
1507         $host = strtolower($h['host']);
1508
1509         // always allow our own site
1510
1511         if($host == strtolower($_SERVER['SERVER_NAME']))
1512                 return true;
1513
1514         $fnmatch = function_exists('fnmatch');
1515         $allowed = explode(',',$str_allowed);
1516
1517         if(count($allowed)) {
1518                 foreach($allowed as $a) {
1519                         $pat = strtolower(trim($a));
1520                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1521                                 $found = true; 
1522                                 break;
1523                         }
1524                 }
1525         }
1526         return $found;
1527 }}
1528
1529 // check if email address is allowed to register here.
1530 // Compare against our list (wildcards allowed).
1531 // Returns false if not allowed, true if allowed or if
1532 // allowed list is not configured.
1533
1534 if(! function_exists('allowed_email')) {
1535 function allowed_email($email) {
1536
1537
1538         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1539         if(! $domain)
1540                 return false;
1541
1542         $str_allowed = get_config('system','allowed_email');
1543         if(! $str_allowed)
1544                 return true;
1545
1546         $found = false;
1547
1548         $fnmatch = function_exists('fnmatch');
1549         $allowed = explode(',',$str_allowed);
1550
1551         if(count($allowed)) {
1552                 foreach($allowed as $a) {
1553                         $pat = strtolower(trim($a));
1554                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1555                                 $found = true; 
1556                                 break;
1557                         }
1558                 }
1559         }
1560         return $found;
1561 }}
1562
1563 // Format the like/dislike text for a profile item
1564 // $cnt = number of people who like/dislike the item
1565 // $arr = array of pre-linked names of likers/dislikers
1566 // $type = one of 'like, 'dislike'
1567 // $id  = item id
1568 // returns formatted text
1569
1570 if(! function_exists('format_like')) {
1571 function format_like($cnt,$arr,$type,$id) {
1572         $o = '';
1573         if($cnt == 1)
1574                 $o .= $arr[0] . (($type === 'like') ? t(' likes this.') : t(' doesn\'t like this.')) . EOL ;
1575         else {
1576                 $o .= '<span class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');" >' 
1577                         . $cnt . ' ' . t('people') . '</span> ' . (($type === 'like') ? t('like this.') : t('don\'t like this.')) . EOL ;
1578                 $total = count($arr);
1579                 if($total >= MAX_LIKERS)
1580                         $arr = array_slice($arr, 0, MAX_LIKERS - 1);
1581                 if($total < MAX_LIKERS)
1582                         $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1];
1583                 $str = implode(', ', $arr);
1584                 if($total >= MAX_LIKERS)
1585                         $str .= t(', and ') . $total - MAX_LIKERS . t(' other people');
1586                 $str .= (($type === 'like') ? t(' like this.') : t(' don\'t like this.'));
1587                 $o .= "\t" . '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>';
1588         }
1589         return $o;
1590 }}
1591
1592
1593 // wrapper to load a view template, checking for alternate
1594 // languages before falling back to the default
1595
1596 if(! function_exists('load_view_file')) {
1597 function load_view_file($s) {
1598         $b = basename($s);
1599         $d = dirname($s);
1600         $lang = get_config('system','language');
1601         if($lang === false)
1602                 $lang = 'en';
1603         if(file_exists("$d/$lang/$b"))
1604                 return file_get_contents("$d/$lang/$b");
1605         return file_get_contents($s);
1606 }}
1607
1608 // for html,xml parsing - let's say you've got
1609 // an attribute foobar="class1 class2 class3"
1610 // and you want to find out if it contains 'class3'.
1611 // you can't use a normal sub string search because you
1612 // might match 'notclass3' and a regex to do the job is 
1613 // possible but a bit complicated. 
1614 // pass the attribute string as $attr and the attribute you 
1615 // are looking for as $s - returns true if found, otherwise false
1616
1617 if(! function_exists('attribute_contains')) {
1618 function attribute_contains($attr,$s) {
1619         $a = explode(' ', $attr);
1620         if(count($a) && in_array($s,$a))
1621                 return true;
1622         return false;
1623 }}
1624
1625 if(! function_exists('logger')) {
1626 function logger($msg,$level = 0) {
1627
1628         $debugging = get_config('system','debugging');
1629         $loglevel  = intval(get_config('system','loglevel'));
1630         $logfile   = get_config('system','logfile');
1631
1632         if((! $debugging) || (! $logfile) || ($level > $loglevel))
1633                 return;
1634         
1635         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
1636         return;
1637 }}
1638
1639
1640 if(! function_exists('activity_match')) {
1641 function activity_match($haystack,$needle) {
1642         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
1643                 return true;
1644         return false;
1645 }}
1646
1647
1648 // Pull out all #hashtags and @person tags from $s;
1649 // We also get @person@domain.com - which would make 
1650 // the regex quite complicated as tags can also
1651 // end a sentence. So we'll run through our results
1652 // and strip the period from any tags which end with one.
1653 // Returns array of tags found, or empty array.
1654
1655
1656 if(! function_exists('get_tags')) {
1657 function get_tags($s) {
1658         $ret = array();
1659         if(preg_match_all('/([@#][^ \x0D\x0A,:?]*)([ \x0D\x0A,:?]|$)/',$s,$match)) {
1660                 foreach($match[1] as $match) {
1661                         if(strstr($match,"]")) {
1662                                 // we might be inside a bbcode color tag - leave it alone
1663                                 continue;
1664                         }
1665                         if(substr($match,-1,1) === '.')
1666                                 $ret[] = substr($match,0,-1);
1667                         else
1668                                 $ret[] = $match;
1669                 }
1670         }
1671
1672         return $ret;
1673 }}
1674
1675
1676 // quick and dirty quoted_printable encoding
1677
1678 if(! function_exists('qp')) {
1679 function qp($s) {
1680 return str_replace ("%","=",rawurlencode($s));
1681 }} 
1682
1683
1684 if(! function_exists('like_puller')) {
1685 function like_puller($a,$item,&$arr,$mode) {
1686
1687         $url = '';
1688         $sparkle = '';
1689         $verb = (($mode === 'like') ? ACTIVITY_LIKE : ACTIVITY_DISLIKE);
1690
1691         if((activity_match($item['verb'],$verb)) && ($item['id'] != $item['parent'])) {
1692                 $url = $item['author-link'];
1693                 if(($item['network'] === 'dfrn') && (! $item['self']) && ($item['author-link'] == $item['url'])) {
1694                         $url = $a->get_baseurl() . '/redir/' . $item['contact-id'];
1695                         $sparkle = ' class="sparkle" ';
1696                 }
1697                 if(! ((isset($arr[$item['parent'] . '-l'])) && (is_array($arr[$item['parent'] . '-l']))))
1698                         $arr[$item['parent'] . '-l'] = array();
1699                 if(! isset($arr[$item['parent']]))
1700                         $arr[$item['parent']] = 1;
1701                 else    
1702                         $arr[$item['parent']] ++;
1703                 $arr[$item['parent'] . '-l'][] = '<a href="'. $url . '"'. $sparkle .'>' . $item['author-name'] . '</a>';
1704         }
1705         return;
1706 }}
1707
1708 if(! function_exists('get_mentions')) {
1709 function get_mentions($item) {
1710         $o = '';
1711         if(! strlen($item['tag']))
1712                 return $o;
1713
1714         $arr = explode(',',$item['tag']);
1715         foreach($arr as $x) {
1716                 $matches = null;
1717                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches))
1718                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
1719         }
1720         return $o;
1721 }}
1722
1723 if(! function_exists('contact_block')) {
1724 function contact_block() {
1725         $o = '';
1726         $a = get_app();
1727         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
1728                 return $o;
1729         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
1730                         intval($a->profile['uid'])
1731         );
1732         if(count($r)) {
1733                 $total = intval($r[0]['total']);
1734         }
1735         if(! $total) {
1736                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
1737                 return $o;
1738         }
1739         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT 24",
1740                         intval($a->profile['uid'])
1741         );
1742         if(count($r)) {
1743                 $o .= '<h4 class="contact-h4">' . $total . ' ' . t('Contacts') . '</h4><div id="contact-block">';
1744                 foreach($r as $rr) {
1745                         $redirect_url = $a->get_baseurl() . '/redir/' . $rr['id'];
1746                         if(local_user() && ($rr['uid'] == local_user())
1747                                 && ($rr['network'] === 'dfrn')) {
1748                                 $url = $redirect_url;
1749                                 $sparkle = ' sparkle';
1750                         }
1751                         else {
1752                                 $url = $rr['url'];
1753                                 $sparkle = '';
1754                         }
1755
1756                         $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";
1757                 }
1758                 $o .= '</div><div id="contact-block-end"></div>';
1759                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
1760                 
1761         }
1762         return $o;
1763
1764 }}
1765
1766 if(! function_exists('search')) {
1767 function search($s) {
1768         $a = get_app();
1769         $o  = '<div id="search-box">';
1770         $o .= '<form action="' . $a->get_baseurl() . '/search' . '" method="get" >';
1771         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
1772         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
1773         $o .= '</form></div>';
1774         return $o;
1775 }}
1776
1777 if(! function_exists('valid_email')) {
1778 function valid_email($x){
1779         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
1780                 return true;
1781         return false;
1782 }}
1783
1784
1785 if(! function_exists('gravatar_img')) {
1786 function gravatar_img($email) {
1787         $size = 175;
1788         $opt = 'identicon';   // psuedo-random geometric pattern if not found
1789         $rating = 'pg';
1790         $hash = md5(trim(strtolower($email)));
1791         
1792         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
1793                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
1794
1795         logger('gravatar: ' . $email . ' ' . $url);
1796         return $url;
1797 }}
1798
1799 if(! function_exists('aes_decrypt')) {
1800 function aes_decrypt($val,$ky)
1801 {
1802     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
1803     for($a=0;$a<strlen($ky);$a++)
1804       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
1805     $mode = MCRYPT_MODE_ECB;
1806     $enc = MCRYPT_RIJNDAEL_128;
1807     $dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) );
1808     return rtrim($dec,(( ord(substr($dec,strlen($dec)-1,1))>=0 and ord(substr($dec, strlen($dec)-1,1))<=16)? chr(ord( substr($dec,strlen($dec)-1,1))):null));
1809 }}
1810
1811
1812 if(! function_exists('aes_encrypt')) {
1813 function aes_encrypt($val,$ky)
1814 {
1815     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
1816     for($a=0;$a<strlen($ky);$a++)
1817       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
1818     $mode=MCRYPT_MODE_ECB;
1819     $enc=MCRYPT_RIJNDAEL_128;
1820     $val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16)));
1821     return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM));
1822 }} 
1823
1824
1825 /**
1826  *
1827  * Function: linkify
1828  *
1829  * Replace naked text hyperlink with HTML formatted hyperlink
1830  *
1831  */
1832
1833 if(! function_exists('linkify')) {
1834 function linkify($s) {
1835         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\.\=\_\~\#\'\%]*)/", ' <a href="$1" >$1</a>', $s);
1836         return($s);
1837 }}
1838
1839
1840 /**
1841  * 
1842  * Function: smilies
1843  *
1844  * Description:
1845  * Replaces text emoticons with graphical images
1846  *
1847  * @Parameter: string $s
1848  *
1849  * Returns string
1850  */
1851
1852 if(! function_exists('smilies')) {
1853 function smilies($s) {
1854         $a = get_app();
1855
1856         return str_replace(
1857         array( ':-)', ';-)', ':-(', ':(', ':-P', ':-"', ':-x', ':-X', ':-D', '8-|', '8-O'),
1858         array(
1859                 '<img src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
1860                 '<img src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
1861                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
1862                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":(" />',
1863                 '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
1864                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
1865                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
1866                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
1867                 '<img src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
1868                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
1869                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />'
1870         ), $s);
1871 }}
1872
1873
1874 /**
1875  *
1876  * Function : profile_load
1877  * @parameter App    $a
1878  * @parameter string $nickname
1879  * @parameter int    $profile
1880  *
1881  * Summary: Loads a profile into the page sidebar. 
1882  * The function requires a writeable copy of the main App structure, and the nickname
1883  * of a registered local account.
1884  *
1885  * If the viewer is an authenticated remote viewer, the profile displayed is the
1886  * one that has been configured for his/her viewing in the Contact manager.
1887  * Passing a non-zero profile ID can also allow a preview of a selected profile
1888  * by the owner.
1889  *
1890  * Profile information is placed in the App structure for later retrieval.
1891  * Honours the owner's chosen theme for display. 
1892  *
1893  */
1894
1895 if(! function_exists('profile_load')) {
1896 function profile_load(&$a, $nickname, $profile = 0) {
1897         if(remote_user()) {
1898                 $r = q("SELECT `profile-id` FROM `contact` WHERE `id` = %d LIMIT 1",
1899                         intval($_SESSION['visitor_id']));
1900                 if(count($r))
1901                         $profile = $r[0]['profile-id'];
1902         } 
1903
1904         $r = null;
1905
1906         if($profile) {
1907                 $profile_int = intval($profile);
1908                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
1909                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
1910                         WHERE `user`.`nickname` = '%s' AND `profile`.`id` = %d LIMIT 1",
1911                         dbesc($nickname),
1912                         intval($profile_int)
1913                 );
1914         }
1915         if(! count($r)) {       
1916                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
1917                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
1918                         WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 LIMIT 1",
1919                         dbesc($nickname)
1920                 );
1921         }
1922
1923         if(($r === false) || (! count($r))) {
1924                 notice( t('No profile') . EOL );
1925                 $a->error = 404;
1926                 return;
1927         }
1928
1929         $a->profile = $r[0];
1930
1931
1932         $a->page['title'] = $a->profile['name'];
1933         $_SESSION['theme'] = $a->profile['theme'];
1934
1935         if(! (x($a->page,'aside')))
1936                 $a->page['aside'] = '';
1937
1938         $a->page['aside'] .= profile_sidebar($a->profile);
1939         $a->page['aside'] .= contact_block();
1940
1941         return;
1942 }}
1943
1944
1945 /**
1946  *
1947  * Function: profile_sidebar
1948  *
1949  * Formats a profile for display in the sidebar.
1950  * It is very difficult to templatise the HTML completely
1951  * because of all the conditional logic.
1952  *
1953  * @parameter: array $profile
1954  *
1955  * Returns HTML string stuitable for sidebar inclusion
1956  * Exceptions: Returns empty string if passed $profile is wrong type or not populated
1957  *
1958  */
1959
1960
1961 if(! function_exists('profile_sidebar')) {
1962 function profile_sidebar($profile) {
1963
1964         $o = '';
1965         $location = '';
1966         $address = false;
1967
1968         if((! is_array($profile)) && (! count($profile)))
1969                 return $o;
1970
1971         $fullname = '<div class="fn">' . $profile['name'] . '</div>';
1972
1973         $tabs = '';
1974
1975         $photo = '<div id="profile=photo-wrapper"><img class="photo" src="' . $profile['photo'] . '" alt="' . $profile['name'] . '" /></div>';
1976
1977         $connect = (($profile['uid'] != local_user()) ? '<li><a id="dfrn-request-link" href="dfrn_request/' . $profile['nickname'] . '">' . t('Connect') . '</a></li>' : '');
1978  
1979         if((x($profile,'address') == 1) 
1980                 || (x($profile,'locality') == 1) 
1981                 || (x($profile,'region') == 1) 
1982                 || (x($profile,'postal-code') == 1) 
1983                 || (x($profile,'country-name') == 1))
1984                 $address = true;
1985
1986         if($address) {
1987                 $location .= '<div class="location"><span class="location-label">' . t('Location:') . '</span> <div class="adr">';
1988                 $location .= ((x($profile,'address') == 1) ? '<div class="street-address">' . $profile['address'] . '</div>' : '');
1989                 $location .= (((x($profile,'locality') == 1) || (x($profile,'region') == 1) || (x($profile,'postal-code') == 1)) 
1990                         ? '<span class="city-state-zip"><span class="locality">' . $profile['locality'] . '</span>' 
1991                         . ((x($profile['locality']) == 1) ? t(', ') : '') 
1992                         . '<span class="region">' . $profile['region'] . '</span>'
1993                         . ' <span class="postal-code">' . $profile['postal-code'] . '</span></span>' : '');
1994                 $location .= ((x($profile,'country-name') == 1) ? ' <span class="country-name">' . $profile['country-name'] . '</span>' : '');  
1995                 $location .= '</div></div><div class="profile-clear"></div>';
1996
1997         }
1998
1999         $gender = ((x($profile,'gender') == 1) ? '<div class="mf"><span class="gender-label">' . t('Gender:') . '</span> <span class="x-gender">' . $profile['gender'] . '</span></div><div class="profile-clear"></div>' : '');
2000
2001         $pubkey = ((x($profile,'pubkey') == 1) ? '<div class="key" style="display:none;">' . $profile['pubkey'] . '</div>' : '');
2002
2003         $marital = ((x($profile,'marital') == 1) ? '<div class="marital"><span class="marital-label"><span class="heart">&hearts;</span> ' . t('Status:') . ' </span><span class="marital-text">' . $profile['marital'] . '</span></div></div><div class="profile-clear"></div>' : '');
2004
2005         $homepage = ((x($profile,'homepage') == 1) ? '<div class="homepage"><span class="homepage-label">' . t('Homepage:') . ' </span><span class="homepage-url">' . linkify($profile['homepage']) . '</span></div></div><div class="profile-clear"></div>' : '');
2006
2007         $tpl = load_view_file('view/profile_vcard.tpl');
2008
2009         $o .= replace_macros($tpl, array(
2010                 '$fullname' => $fullname,
2011                 '$tabs'     => $tabs,
2012                 '$photo'    => $photo,
2013                 '$connect'  => $connect,                
2014                 '$location' => $location,
2015                 '$gender'   => $gender,
2016                 '$pubkey'   => $pubkey,
2017                 '$marital'  => $marital,
2018                 '$homepage' => $homepage
2019         ));
2020
2021         return $o;
2022 }}
2023
2024
2025 if(! function_exists('register_hook')) {
2026 function register_hook($hook,$file,$function) {
2027
2028         $r = q("INSERT INTO `hook` (`hook`, `file`, `function`) VALUES ( '%s', '%s', '%s' ) ",
2029                 dbesc($hook),
2030                 dbesc($file),
2031                 dbesc($function)
2032         );
2033         return $r;
2034 }}
2035
2036 if(! function_exists('unregister_hook')) {
2037 function unregister_hook($hook,$file,$function) {
2038
2039         $r = q("DELETE FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2040                 dbesc($hook),
2041                 dbesc($file),
2042                 dbesc($function)
2043         );
2044         return $r;
2045 }}
2046
2047
2048 if(! function_exists('load_hooks')) {
2049 function load_hooks() {
2050         $a = get_app();
2051         $r = q("SELECT * FROM `hook` WHERE 1");
2052         if(count($r)) {
2053                 foreach($r as $rr) {
2054                         $a->hooks[] = array($rr['hook'], $rr['file'], $rr['function']);
2055                 }
2056         }
2057 }}
2058
2059
2060 if(! function_exists('call_hooks')) {
2061 function call_hooks($name, $data = null) {
2062         $a = get_app();
2063
2064         if(count($a->hooks)) {
2065                 foreach($a->hooks as $hook) {
2066                         if($hook[HOOK_HOOK] === $name) {
2067                                 @include_once($hook[HOOK_FILE]);
2068                                 if(function_exists($hook[HOOK_FUNCTION])) {
2069                                         $func = $hook[HOOK_FUNCTION];
2070                                         $func($a,$data);
2071                                 }
2072                         }
2073                 }
2074         }
2075 }}
2076