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