]> git.mxchange.org Git - friendica.git/blob - include/text.php
8f15ff294517e8d86e936b6ee4fd9ce067119ec8
[friendica.git] / include / text.php
1 <?php
2
3 require_once("include/template_processor.php");
4 require_once("include/friendica_smarty.php");
5 require_once("include/Smilies.php");
6 require_once("include/map.php");
7 require_once("mod/proxy.php");
8
9
10 if(! function_exists('replace_macros')) {
11 /**
12  * This is our template processor
13  *
14  * @param string|FriendicaSmarty $s the string requiring macro substitution,
15  *                                                                      or an instance of FriendicaSmarty
16  * @param array $r key value pairs (search => replace)
17  * @return string substituted string
18  */
19 function replace_macros($s,$r) {
20
21         $stamp1 = microtime(true);
22
23         $a = get_app();
24
25         // pass $baseurl to all templates
26         $r['$baseurl'] = $a->get_baseurl();
27
28
29         $t = $a->template_engine();
30         try {
31                 $output = $t->replace_macros($s,$r);
32         } catch (Exception $e) {
33                 echo "<pre><b>".__function__."</b>: ".$e->getMessage()."</pre>"; killme();
34         }
35
36         $a->save_timestamp($stamp1, "rendering");
37
38         return $output;
39 }}
40
41
42 // random string, there are 86 characters max in text mode, 128 for hex
43 // output is urlsafe
44
45 define('RANDOM_STRING_HEX',  0x00 );
46 define('RANDOM_STRING_TEXT', 0x01 );
47
48 if(! function_exists('random_string')) {
49 function random_string($size = 64,$type = RANDOM_STRING_HEX) {
50         // generate a bit of entropy and run it through the whirlpool
51         $s = hash('whirlpool', (string) rand() . uniqid(rand(),true) . (string) rand(),(($type == RANDOM_STRING_TEXT) ? true : false));
52         $s = (($type == RANDOM_STRING_TEXT) ? str_replace("\n","",base64url_encode($s,true)) : $s);
53         return(substr($s,0,$size));
54 }}
55
56 if(! function_exists('notags')) {
57 /**
58  * This is our primary input filter.
59  *
60  * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
61  * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
62  * after cleansing, and angle chars with the high bit set could get through as markup.
63  *
64  * This is now disabled because it was interfering with some legitimate unicode sequences
65  * and hopefully there aren't a lot of those browsers left.
66  *
67  * Use this on any text input where angle chars are not valid or permitted
68  * They will be replaced with safer brackets. This may be filtered further
69  * if these are not allowed either.
70  *
71  * @param string $string Input string
72  * @return string Filtered string
73  */
74 function notags($string) {
75
76         return(str_replace(array("<",">"), array('[',']'), $string));
77
78 //  High-bit filter no longer used
79 //      return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
80 }}
81
82
83
84 if(! function_exists('escape_tags')) {
85 /**
86  * use this on "body" or "content" input where angle chars shouldn't be removed,
87  * and allow them to be safely displayed.
88  * @param string $string
89  * @return string
90  */
91 function escape_tags($string) {
92
93         return(htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false));
94 }}
95
96
97 // generate a string that's random, but usually pronounceable.
98 // used to generate initial passwords
99
100 if(! function_exists('autoname')) {
101 /**
102  * generate a string that's random, but usually pronounceable.
103  * used to generate initial passwords
104  * @param int $len
105  * @return string
106  */
107 function autoname($len) {
108
109         if($len <= 0)
110                 return '';
111
112         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u');
113         if(mt_rand(0,5) == 4)
114                 $vowels[] = 'y';
115
116         $cons = array(
117                         'b','bl','br',
118                         'c','ch','cl','cr',
119                         'd','dr',
120                         'f','fl','fr',
121                         'g','gh','gl','gr',
122                         'h',
123                         'j',
124                         'k','kh','kl','kr',
125                         'l',
126                         'm',
127                         'n',
128                         'p','ph','pl','pr',
129                         'qu',
130                         'r','rh',
131                         's','sc','sh','sm','sp','st',
132                         't','th','tr',
133                         'v',
134                         'w','wh',
135                         'x',
136                         'z','zh'
137                         );
138
139         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
140                                 'nd','ng','nk','nt','rn','rp','rt');
141
142         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
143                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
144
145         $start = mt_rand(0,2);
146         if($start == 0)
147                 $table = $vowels;
148         else
149                 $table = $cons;
150
151         $word = '';
152
153         for ($x = 0; $x < $len; $x ++) {
154                 $r = mt_rand(0,count($table) - 1);
155                 $word .= $table[$r];
156
157                 if($table == $vowels)
158                         $table = array_merge($cons,$midcons);
159                 else
160                         $table = $vowels;
161
162         }
163
164         $word = substr($word,0,$len);
165
166         foreach($noend as $noe) {
167                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
168                         $word = substr($word,0,-1);
169                         break;
170                 }
171         }
172         if(substr($word,-1) == 'q')
173                 $word = substr($word,0,-1);
174         return $word;
175 }}
176
177
178 // escape text ($str) for XML transport
179 // returns escaped text.
180
181 if(! function_exists('xmlify')) {
182 /**
183  * escape text ($str) for XML transport
184  * @param string $str
185  * @return string Escaped text.
186  */
187 function xmlify($str) {
188 /*      $buffer = '';
189
190         $len = mb_strlen($str);
191         for($x = 0; $x < $len; $x ++) {
192                 $char = mb_substr($str,$x,1);
193
194                 switch( $char ) {
195
196                         case "\r" :
197                                 break;
198                         case "&" :
199                                 $buffer .= '&amp;';
200                                 break;
201                         case "'" :
202                                 $buffer .= '&apos;';
203                                 break;
204                         case "\"" :
205                                 $buffer .= '&quot;';
206                                 break;
207                         case '<' :
208                                 $buffer .= '&lt;';
209                                 break;
210                         case '>' :
211                                 $buffer .= '&gt;';
212                                 break;
213                         case "\n" :
214                                 $buffer .= "\n";
215                                 break;
216                         default :
217                                 $buffer .= $char;
218                                 break;
219                 }
220         }*/
221         /*
222         $buffer = mb_ereg_replace("&", "&amp;", $str);
223         $buffer = mb_ereg_replace("'", "&apos;", $buffer);
224         $buffer = mb_ereg_replace('"', "&quot;", $buffer);
225         $buffer = mb_ereg_replace("<", "&lt;", $buffer);
226         $buffer = mb_ereg_replace(">", "&gt;", $buffer);
227         */
228         $buffer = htmlspecialchars($str, ENT_QUOTES, "UTF-8");
229         $buffer = trim($buffer);
230
231         return($buffer);
232 }}
233
234 if(! function_exists('unxmlify')) {
235 /**
236  * undo an xmlify
237  * @param string $s xml escaped text
238  * @return string unescaped text
239  */
240 function unxmlify($s) {
241 //      $ret = str_replace('&amp;','&', $s);
242 //      $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
243         /*$ret = mb_ereg_replace('&amp;', '&', $s);
244         $ret = mb_ereg_replace('&apos;', "'", $ret);
245         $ret = mb_ereg_replace('&quot;', '"', $ret);
246         $ret = mb_ereg_replace('&lt;', "<", $ret);
247         $ret = mb_ereg_replace('&gt;', ">", $ret);
248         */
249         $ret = htmlspecialchars_decode($s, ENT_QUOTES);
250         return $ret;
251 }}
252
253 if(! function_exists('hex2bin')) {
254 /**
255  * convenience wrapper, reverse the operation "bin2hex"
256  * @param string $s
257  * @return number
258  */
259 function hex2bin($s) {
260         if(! (is_string($s) && strlen($s)))
261                 return '';
262
263         if(! ctype_xdigit($s)) {
264                 return($s);
265         }
266
267         return(pack("H*",$s));
268 }}
269
270
271 if(! function_exists('paginate_data')) {
272 /**
273  * Automatica pagination data.
274  *
275  * @param App $a App instance
276  * @param int $count [optional] item count (used with alt pager)
277  * @return Array data for pagination template
278  */
279 function paginate_data(&$a, $count=null) {
280         $stripped = preg_replace('/([&?]page=[0-9]*)/','',$a->query_string);
281
282         $stripped = str_replace('q=','',$stripped);
283         $stripped = trim($stripped,'/');
284         $pagenum = $a->pager['page'];
285
286         if (($a->page_offset != "") AND !preg_match('/[?&].offset=/', $stripped))
287                 $stripped .= "&offset=".urlencode($a->page_offset);
288
289         $url = $stripped;
290
291         $data = array();
292         function _l(&$d, $name, $url, $text, $class="") {
293                 if (!strpos($url, "?")) {
294                         if ($pos = strpos($url, "&"))
295                                 $url = substr($url, 0, $pos)."?".substr($url, $pos + 1);
296                 }
297
298                 $d[$name] = array('url'=>$url, 'text'=>$text, 'class'=>$class);
299         }
300
301         if (!is_null($count)){
302                 // alt pager
303                 if($a->pager['page']>1)
304                         _l($data,  "prev", $url.'&page='.($a->pager['page'] - 1), t('newer'));
305                 if($count>0)
306                         _l($data,  "next", $url.'&page='.($a->pager['page'] + 1), t('older'));
307         } else {
308                 // full pager
309                 if($a->pager['total'] > $a->pager['itemspage']) {
310                         if($a->pager['page'] != 1)
311                                 _l($data,  "prev", $url.'&page='.($a->pager['page'] - 1), t('prev'));
312
313                         _l($data, "first", $url."&page=1",  t('first'));
314
315
316                         $numpages = $a->pager['total'] / $a->pager['itemspage'];
317
318                         $numstart = 1;
319                         $numstop = $numpages;
320
321                         if($numpages > 14) {
322                                 $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
323                                 $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
324                         }
325
326                         $pages = array();
327
328                         for($i = $numstart; $i <= $numstop; $i++){
329                                 if($i == $a->pager['page'])
330                                         _l($pages, $i, "#",  $i, "current");
331                                 else
332                                         _l($pages, $i, $url."&page=$i", $i, "n");
333                         }
334
335                         if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
336                                 if($i == $a->pager['page'])
337                                         _l($pages, $i, "#",  $i, "current");
338                                 else
339                                         _l($pages, $i, $url."&page=$i", $i, "n");
340                         }
341
342                         $data['pages'] = $pages;
343
344                         $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
345                         _l($data, "last", $url."&page=$lastpage", t('last'));
346
347                         if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
348                                 _l($data, "next", $url."&page=".($a->pager['page'] + 1), t('next'));
349
350                 }
351         }
352         return $data;
353
354 }}
355
356 if(! function_exists('paginate')) {
357 /**
358  * Automatic pagination.
359  *
360  *  To use, get the count of total items.
361  * Then call $a->set_pager_total($number_items);
362  * Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
363  * Then call paginate($a) after the end of the display loop to insert the pager block on the page
364  * (assuming there are enough items to paginate).
365  * When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
366  * will limit the results to the correct items for the current page.
367  * The actual page handling is then accomplished at the application layer.
368  *
369  * @param App $a App instance
370  * @return string html for pagination #FIXME remove html
371  */
372 function paginate(&$a) {
373
374         $data = paginate_data($a);
375         $tpl = get_markup_template("paginate.tpl");
376         return replace_macros($tpl, array("pager" => $data));
377
378 }}
379
380 if(! function_exists('alt_pager')) {
381 /**
382  * Alternative pager
383  * @param App $a App instance
384  * @param int $i
385  * @return string html for pagination #FIXME remove html
386  */
387 function alt_pager(&$a, $i) {
388
389         $data = paginate_data($a, $i);
390         $tpl = get_markup_template("paginate.tpl");
391         return replace_macros($tpl, array('pager' => $data));
392
393 }}
394
395 if(! function_exists('scroll_loader')) {
396 /**
397  * Loader for infinite scrolling
398  * @return string html for loader
399  */
400 function scroll_loader() {
401         $tpl = get_markup_template("scroll_loader.tpl");
402         return replace_macros($tpl, array(
403                 'wait' => t('Loading more entries...'),
404                 'end' => t('The end')
405         ));
406 }}
407
408 if(! function_exists('expand_acl')) {
409 /**
410  * Turn user/group ACLs stored as angle bracketed text into arrays
411  *
412  * @param string $s
413  * @return array
414  */
415 function expand_acl($s) {
416         // turn string array of angle-bracketed elements into numeric array
417         // e.g. "<1><2><3>" => array(1,2,3);
418         $ret = array();
419
420         if(strlen($s)) {
421                 $t = str_replace('<','',$s);
422                 $a = explode('>',$t);
423                 foreach($a as $aa) {
424                         if(intval($aa))
425                                 $ret[] = intval($aa);
426                 }
427         }
428         return $ret;
429 }}
430
431 if(! function_exists('sanitise_acl')) {
432 /**
433  * Wrap ACL elements in angle brackets for storage
434  * @param string $item
435  */
436 function sanitise_acl(&$item) {
437         if(intval($item))
438                 $item = '<' . intval(notags(trim($item))) . '>';
439         else
440                 unset($item);
441 }}
442
443
444 if(! function_exists('perms2str')) {
445 /**
446  * Convert an ACL array to a storable string
447  *
448  * Normally ACL permissions will be an array.
449  * We'll also allow a comma-separated string.
450  *
451  * @param string|array $p
452  * @return string
453  */
454 function perms2str($p) {
455         $ret = '';
456         if(is_array($p))
457                 $tmp = $p;
458         else
459                 $tmp = explode(',',$p);
460
461         if(is_array($tmp)) {
462                 array_walk($tmp,'sanitise_acl');
463                 $ret = implode('',$tmp);
464         }
465         return $ret;
466 }}
467
468
469 if(! function_exists('item_new_uri')) {
470 /**
471  * generate a guaranteed unique (for this domain) item ID for ATOM
472  * safe from birthday paradox
473  *
474  * @param string $hostname
475  * @param int $uid
476  * @return string
477  */
478 function item_new_uri($hostname,$uid, $guid = "") {
479
480         do {
481                 $dups = false;
482
483                 if ($guid == "")
484                         $hash = get_guid(32);
485                 else {
486                         $hash = $guid;
487                         $guid = "";
488                 }
489
490                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
491
492                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
493                         dbesc($uri));
494                 if(count($r))
495                         $dups = true;
496         } while($dups == true);
497         return $uri;
498 }}
499
500 // Generate a guaranteed unique photo ID.
501 // safe from birthday paradox
502
503 if(! function_exists('photo_new_resource')) {
504 /**
505  * Generate a guaranteed unique photo ID.
506  * safe from birthday paradox
507  *
508  * @return string
509  */
510 function photo_new_resource() {
511
512         do {
513                 $found = false;
514                 $resource = hash('md5',uniqid(mt_rand(),true));
515                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
516                         dbesc($resource)
517                 );
518                 if(count($r))
519                         $found = true;
520         } while($found == true);
521         return $resource;
522 }}
523
524
525 if(! function_exists('load_view_file')) {
526 /**
527  * @deprecated
528  * wrapper to load a view template, checking for alternate
529  * languages before falling back to the default
530  *
531  * @global string $lang
532  * @global App $a
533  * @param string $s view name
534  * @return string
535  */
536 function load_view_file($s) {
537         global $lang, $a;
538         if(! isset($lang))
539                 $lang = 'en';
540         $b = basename($s);
541         $d = dirname($s);
542         if(file_exists("$d/$lang/$b")) {
543                 $stamp1 = microtime(true);
544                 $content = file_get_contents("$d/$lang/$b");
545                 $a->save_timestamp($stamp1, "file");
546                 return $content;
547         }
548
549         $theme = current_theme();
550
551         if(file_exists("$d/theme/$theme/$b")) {
552                 $stamp1 = microtime(true);
553                 $content = file_get_contents("$d/theme/$theme/$b");
554                 $a->save_timestamp($stamp1, "file");
555                 return $content;
556         }
557
558         $stamp1 = microtime(true);
559         $content = file_get_contents($s);
560         $a->save_timestamp($stamp1, "file");
561         return $content;
562 }}
563
564 if(! function_exists('get_intltext_template')) {
565 /**
566  * load a view template, checking for alternate
567  * languages before falling back to the default
568  *
569  * @global string $lang
570  * @param string $s view path
571  * @return string
572  */
573 function get_intltext_template($s) {
574         global $lang;
575
576         $a = get_app();
577         $engine = '';
578         if($a->theme['template_engine'] === 'smarty3')
579                 $engine = "/smarty3";
580
581         if(! isset($lang))
582                 $lang = 'en';
583
584         if(file_exists("view/lang/$lang$engine/$s")) {
585                 $stamp1 = microtime(true);
586                 $content = file_get_contents("view/lang/$lang$engine/$s");
587                 $a->save_timestamp($stamp1, "file");
588                 return $content;
589         } elseif(file_exists("view/lang/en$engine/$s")) {
590                 $stamp1 = microtime(true);
591                 $content = file_get_contents("view/lang/en$engine/$s");
592                 $a->save_timestamp($stamp1, "file");
593                 return $content;
594         } else {
595                 $stamp1 = microtime(true);
596                 $content = file_get_contents("view$engine/$s");
597                 $a->save_timestamp($stamp1, "file");
598                 return $content;
599         }
600 }}
601
602 if(! function_exists('get_markup_template')) {
603 /**
604  * load template $s
605  *
606  * @param string $s
607  * @param string $root
608  * @return string
609  */
610 function get_markup_template($s, $root = '') {
611         $stamp1 = microtime(true);
612
613         $a = get_app();
614         $t = $a->template_engine();
615         try {
616                 $template = $t->get_template_file($s, $root);
617         } catch (Exception $e) {
618                 echo "<pre><b>".__function__."</b>: ".$e->getMessage()."</pre>"; killme();
619         }
620
621         $a->save_timestamp($stamp1, "file");
622
623         return $template;
624 }}
625
626 if(! function_exists("get_template_file")) {
627 /**
628  *
629  * @param App $a
630  * @param string $filename
631  * @param string $root
632  * @return string
633  */
634 function get_template_file($a, $filename, $root = '') {
635         $theme = current_theme();
636
637         // Make sure $root ends with a slash /
638         if($root !== '' && $root[strlen($root)-1] !== '/')
639                 $root = $root . '/';
640
641         if(file_exists("{$root}view/theme/$theme/$filename"))
642                 $template_file = "{$root}view/theme/$theme/$filename";
643         elseif (x($a->theme_info,"extends") && file_exists("{$root}view/theme/{$a->theme_info["extends"]}/$filename"))
644                 $template_file = "{$root}view/theme/{$a->theme_info["extends"]}/$filename";
645         elseif (file_exists("{$root}/$filename"))
646                 $template_file = "{$root}/$filename";
647         else
648                 $template_file = "{$root}view/$filename";
649
650         return $template_file;
651 }}
652
653
654
655
656
657
658
659 if(! function_exists('attribute_contains')) {
660 /**
661  *  for html,xml parsing - let's say you've got
662  *  an attribute foobar="class1 class2 class3"
663  *  and you want to find out if it contains 'class3'.
664  *  you can't use a normal sub string search because you
665  *  might match 'notclass3' and a regex to do the job is
666  *  possible but a bit complicated.
667  *  pass the attribute string as $attr and the attribute you
668  *  are looking for as $s - returns true if found, otherwise false
669  *
670  * @param string $attr attribute value
671  * @param string $s string to search
672  * @return boolean True if found, False otherwise
673  */
674 function attribute_contains($attr,$s) {
675         $a = explode(' ', $attr);
676         if(count($a) && in_array($s,$a))
677                 return true;
678         return false;
679 }}
680
681 if (! function_exists('logger')) {
682 /* setup int->string log level map */
683 $LOGGER_LEVELS = array();
684
685 /**
686  * @brief Logs the given message at the given log level
687  *
688  * log levels:
689  * LOGGER_NORMAL (default)
690  * LOGGER_TRACE
691  * LOGGER_DEBUG
692  * LOGGER_DATA
693  * LOGGER_ALL
694  *
695  * @global App $a
696  * @global dba $db
697  * @global array $LOGGER_LEVELS
698  * @param string $msg
699  * @param int $level
700  */
701 function logger($msg, $level = 0) {
702         global $a;
703         global $db;
704         global $LOGGER_LEVELS;
705
706         $debugging = get_config('system','debugging');
707         $logfile   = get_config('system','logfile');
708         $loglevel = intval(get_config('system','loglevel'));
709
710         // turn off logger in install mode
711         if (
712                 $a->module == 'install'
713                 || ! ($db && $db->connected)
714                 || ! $debugging
715                 || ! $logfile
716                 || $level > $loglevel
717         ) {
718                 return;
719         }
720
721         if (count($LOGGER_LEVELS) == 0) {
722                 foreach (get_defined_constants() as $k => $v) {
723                         if (substr($k, 0, 7) == "LOGGER_") {
724                                 $LOGGER_LEVELS[$v] = substr($k, 7, 7);
725                         }
726                 }
727         }
728
729         $process_id = session_id();
730
731         if ($process_id == '') {
732                 $process_id = get_app()->process_id;
733         }
734
735         $callers = debug_backtrace();
736         $logline = sprintf("%s@%s\t[%s]:%s:%s:%s\t%s\n",
737                 datetime_convert(),
738                 $process_id,
739                 $LOGGER_LEVELS[$level],
740                 basename($callers[0]['file']),
741                 $callers[0]['line'],
742                 $callers[1]['function'],
743                 $msg
744         );
745
746         $stamp1 = microtime(true);
747         @file_put_contents($logfile, $logline, FILE_APPEND);
748         $a->save_timestamp($stamp1, "file");
749 }}
750
751
752 if(! function_exists('activity_match')) {
753 /**
754  * Compare activity uri. Knows about activity namespace.
755  *
756  * @param string $haystack
757  * @param string $needle
758  * @return boolean
759  */
760 function activity_match($haystack,$needle) {
761         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
762                 return true;
763         return false;
764 }}
765
766
767 if(! function_exists('get_tags')) {
768 /**
769  * Pull out all #hashtags and @person tags from $s;
770  * We also get @person@domain.com - which would make
771  * the regex quite complicated as tags can also
772  * end a sentence. So we'll run through our results
773  * and strip the period from any tags which end with one.
774  * Returns array of tags found, or empty array.
775  *
776  * @param string $s
777  * @return array
778  */
779 function get_tags($s) {
780         $ret = array();
781
782         // Convert hashtag links to hashtags
783         $s = preg_replace("/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism", "#$2", $s);
784
785         // ignore anything in a code block
786         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
787
788         // Force line feeds at bbtags
789         $s = str_replace(array("[", "]"), array("\n[", "]\n"), $s);
790
791         // ignore anything in a bbtag
792         $s = preg_replace('/\[(.*?)\]/sm','',$s);
793
794         // Match full names against @tags including the space between first and last
795         // We will look these up afterward to see if they are full names or not recognisable.
796
797         if(preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/',$s,$match)) {
798                 foreach($match[1] as $mtch) {
799                         if(strstr($mtch,"]")) {
800                                 // we might be inside a bbcode color tag - leave it alone
801                                 continue;
802                         }
803                         if(substr($mtch,-1,1) === '.')
804                                 $ret[] = substr($mtch,0,-1);
805                         else
806                                 $ret[] = $mtch;
807                 }
808         }
809
810         // Otherwise pull out single word tags. These can be @nickname, @first_last
811         // and #hash tags.
812
813         if(preg_match_all('/([!#@][^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/',$s,$match)) {
814                 foreach($match[1] as $mtch) {
815                         if(strstr($mtch,"]")) {
816                                 // we might be inside a bbcode color tag - leave it alone
817                                 continue;
818                         }
819                         if(substr($mtch,-1,1) === '.')
820                                 $mtch = substr($mtch,0,-1);
821                         // ignore strictly numeric tags like #1
822                         if((strpos($mtch,'#') === 0) && ctype_digit(substr($mtch,1)))
823                                 continue;
824                         // try not to catch url fragments
825                         if(strpos($s,$mtch) && preg_match('/[a-zA-z0-9\/]/',substr($s,strpos($s,$mtch)-1,1)))
826                                 continue;
827                         $ret[] = $mtch;
828                 }
829         }
830         return $ret;
831 }}
832
833
834 //
835
836 if(! function_exists('qp')) {
837 /**
838  * quick and dirty quoted_printable encoding
839  *
840  * @param string $s
841  * @return string
842  */
843 function qp($s) {
844 return str_replace ("%","=",rawurlencode($s));
845 }}
846
847 if(! function_exists('contact_block')) {
848 /**
849  * Get html for contact block.
850  *
851  * @template contact_block.tpl
852  * @hook contact_block_end (contacts=>array, output=>string)
853  * @return string
854  */
855 function contact_block() {
856         $o = '';
857         $a = get_app();
858
859         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
860         if($shown === false)
861                 $shown = 24;
862         if($shown == 0)
863                 return;
864
865         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
866                 return $o;
867         $r = q("SELECT COUNT(*) AS `total` FROM `contact`
868                         WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0
869                                 AND `hidden` = 0 AND `archive` = 0
870                                 AND `network` IN ('%s', '%s', '%s')",
871                         intval($a->profile['uid']),
872                         dbesc(NETWORK_DFRN),
873                         dbesc(NETWORK_OSTATUS),
874                         dbesc(NETWORK_DIASPORA)
875         );
876         if(count($r)) {
877                 $total = intval($r[0]['total']);
878         }
879         if(! $total) {
880                 $contacts = t('No contacts');
881                 $micropro = Null;
882
883         } else {
884                 // Splitting the query in two parts makes it much faster
885                 $r = q("SELECT `id` FROM `contact`
886                                 WHERE `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending`
887                                         AND NOT `hidden` AND NOT `archive`
888                                 AND `network` IN ('%s', '%s', '%s') ORDER BY RAND() LIMIT %d",
889                                 intval($a->profile['uid']),
890                                 dbesc(NETWORK_DFRN),
891                                 dbesc(NETWORK_OSTATUS),
892                                 dbesc(NETWORK_DIASPORA),
893                                 intval($shown)
894                 );
895                 if ($r) {
896                         $contacts = "";
897                         foreach ($r AS $contact)
898                                 $contacts[] = $contact["id"];
899
900                         $r = q("SELECT `id`, `uid`, `addr`, `url`, `name`, `thumb`, `network` FROM `contact` WHERE `id` IN (%s)",
901                                 dbesc(implode(",", $contacts)));
902                         if(count($r)) {
903                                 $contacts = sprintf( tt('%d Contact','%d Contacts', $total),$total);
904                                 $micropro = Array();
905                                 foreach($r as $rr) {
906                                         $micropro[] = micropro($rr,true,'mpfriend');
907                                 }
908                         }
909                 }
910         }
911
912         $tpl = get_markup_template('contact_block.tpl');
913         $o = replace_macros($tpl, array(
914                 '$contacts' => $contacts,
915                 '$nickname' => $a->profile['nickname'],
916                 '$viewcontacts' => t('View Contacts'),
917                 '$micropro' => $micropro,
918         ));
919
920         $arr = array('contacts' => $r, 'output' => $o);
921
922         call_hooks('contact_block_end', $arr);
923         return $o;
924
925 }}
926
927 /**
928  * @brief Format contacts as picture links or as texxt links
929  *
930  * @param array $contact Array with contacts which contains an array with
931  *      int 'id' => The ID of the contact
932  *      int 'uid' => The user ID of the user who owns this data
933  *      string 'name' => The name of the contact
934  *      string 'url' => The url to the profile page of the contact
935  *      string 'addr' => The webbie of the contact (e.g.) username@friendica.com
936  *      string 'network' => The network to which the contact belongs to
937  *      string 'thumb' => The contact picture
938  *      string 'click' => js code which is performed when clicking on the contact
939  * @param boolean $redirect If true try to use the redir url if it's possible
940  * @param string $class CSS class for the
941  * @param boolean $textmode If true display the contacts as text links
942  *      if false display the contacts as picture links
943
944  * @return string Formatted html
945  */
946 function micropro($contact, $redirect = false, $class = '', $textmode = false) {
947
948         // Use the contact URL if no address is available
949         if ($contact["addr"] == "")
950                 $contact["addr"] = $contact["url"];
951
952         $url = $contact['url'];
953         $sparkle = '';
954         $redir = false;
955
956         if($redirect) {
957                 $a = get_app();
958                 $redirect_url = 'redir/' . $contact['id'];
959                 if(local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === NETWORK_DFRN)) {
960                         $redir = true;
961                         $url = $redirect_url;
962                         $sparkle = ' sparkle';
963                 }
964                 else
965                         $url = zrl($url);
966         }
967
968         // If there is some js available we don't need the url
969         if(x($contact,'click'))
970                 $url = '';
971
972         return replace_macros(get_markup_template(($textmode)?'micropro_txt.tpl':'micropro_img.tpl'),array(
973                 '$click' => (($contact['click']) ? $contact['click'] : ''),
974                 '$class' => $class,
975                 '$url' => $url,
976                 '$photo' => proxy_url($contact['thumb'], false, PROXY_SIZE_THUMB),
977                 '$name' => $contact['name'],
978                 'title' => $contact['name'] . ' [' . $contact['addr'] . ']',
979                 '$parkle' => $sparkle,
980                 '$redir' => $redir,
981
982         ));
983 }
984
985
986
987 if(! function_exists('search')) {
988 /**
989  * search box
990  *
991  * @param string $s search query
992  * @param string $id html id
993  * @param string $url search url
994  * @param boolean $savedsearch show save search button
995  */
996 function search($s,$id='search-box',$url='search',$save = false, $aside = true) {
997         $a = get_app();
998
999         $values = array(
1000                         '$s' => htmlspecialchars($s),
1001                         '$id' => $id,
1002                         '$action_url' => $url,
1003                         '$search_label' => t('Search'),
1004                         '$save_label' => t('Save'),
1005                         '$savedsearch' => feature_enabled(local_user(),'savedsearch'),
1006                         '$search_hint' => t('@name, !forum, #tags, content'),
1007                 );
1008
1009         if (!$aside) {
1010                 $values['$searchoption'] = array(
1011                                         t("Full Text"),
1012                                         t("Tags"),
1013                                         t("Contacts"));
1014
1015                 if (get_config('system','poco_local_search'))
1016                         $values['$searchoption'][] = t("Forums");
1017         }
1018
1019         return replace_macros(get_markup_template('searchbox.tpl'), $values);
1020 }}
1021
1022 if(! function_exists('valid_email')) {
1023 /**
1024  * Check if $x is a valid email string
1025  *
1026  * @param string $x
1027  * @return boolean
1028  */
1029 function valid_email($x){
1030
1031         // Removed because Fabio told me so.
1032         //if(get_config('system','disable_email_validation'))
1033         //      return true;
1034
1035         if(preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
1036                 return true;
1037         return false;
1038 }}
1039
1040
1041 if(! function_exists('linkify')) {
1042 /**
1043  * Replace naked text hyperlink with HTML formatted hyperlink
1044  *
1045  * @param string $s
1046  */
1047 function linkify($s) {
1048         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="_blank">$1</a>', $s);
1049         $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$s);
1050         return($s);
1051 }}
1052
1053
1054 /**
1055  * Load poke verbs
1056  *
1057  * @return array index is present tense verb
1058                                  value is array containing past tense verb, translation of present, translation of past
1059  * @hook poke_verbs pokes array
1060  */
1061 function get_poke_verbs() {
1062
1063         // index is present tense verb
1064         // value is array containing past tense verb, translation of present, translation of past
1065
1066         $arr = array(
1067                 'poke' => array( 'poked', t('poke'), t('poked')),
1068                 'ping' => array( 'pinged', t('ping'), t('pinged')),
1069                 'prod' => array( 'prodded', t('prod'), t('prodded')),
1070                 'slap' => array( 'slapped', t('slap'), t('slapped')),
1071                 'finger' => array( 'fingered', t('finger'), t('fingered')),
1072                 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')),
1073         );
1074         call_hooks('poke_verbs', $arr);
1075         return $arr;
1076 }
1077
1078 /**
1079  * Load moods
1080  * @return array index is mood, value is translated mood
1081  * @hook mood_verbs moods array
1082  */
1083 function get_mood_verbs() {
1084
1085         $arr = array(
1086                 'happy'      => t('happy'),
1087                 'sad'        => t('sad'),
1088                 'mellow'     => t('mellow'),
1089                 'tired'      => t('tired'),
1090                 'perky'      => t('perky'),
1091                 'angry'      => t('angry'),
1092                 'stupefied'  => t('stupified'),
1093                 'puzzled'    => t('puzzled'),
1094                 'interested' => t('interested'),
1095                 'bitter'     => t('bitter'),
1096                 'cheerful'   => t('cheerful'),
1097                 'alive'      => t('alive'),
1098                 'annoyed'    => t('annoyed'),
1099                 'anxious'    => t('anxious'),
1100                 'cranky'     => t('cranky'),
1101                 'disturbed'  => t('disturbed'),
1102                 'frustrated' => t('frustrated'),
1103                 'motivated'  => t('motivated'),
1104                 'relaxed'    => t('relaxed'),
1105                 'surprised'  => t('surprised'),
1106         );
1107
1108         call_hooks('mood_verbs', $arr);
1109         return $arr;
1110 }
1111
1112 if(! function_exists('day_translate')) {
1113 /**
1114  * Translate days and months names
1115  *
1116  * @param string $s
1117  * @return string
1118  */
1119 function day_translate($s) {
1120         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
1121                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
1122                 $s);
1123
1124         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
1125                 array( t('January'), t('February'), t('March'), t('April'), t('May'), t('June'), t('July'), t('August'), t('September'), t('October'), t('November'), t('December')),
1126                 $ret);
1127
1128         return $ret;
1129 }}
1130
1131
1132 if(! function_exists('normalise_link')) {
1133 /**
1134  * Normalize url
1135  *
1136  * @param string $url
1137  * @return string
1138  */
1139 function normalise_link($url) {
1140         $ret = str_replace(array('https:','//www.'), array('http:','//'), $url);
1141         return(rtrim($ret,'/'));
1142 }}
1143
1144
1145
1146 if(! function_exists('link_compare')) {
1147 /**
1148  * Compare two URLs to see if they are the same, but ignore
1149  * slight but hopefully insignificant differences such as if one
1150  * is https and the other isn't, or if one is www.something and
1151  * the other isn't - and also ignore case differences.
1152  *
1153  * @param string $a first url
1154  * @param string $b second url
1155  * @return boolean True if the URLs match, otherwise False
1156  *
1157  */
1158 function link_compare($a,$b) {
1159         if(strcasecmp(normalise_link($a),normalise_link($b)) === 0)
1160                 return true;
1161         return false;
1162 }}
1163
1164
1165 if(! function_exists('redir_private_images')) {
1166 /**
1167  * Find any non-embedded images in private items and add redir links to them
1168  *
1169  * @param App $a
1170  * @param array $item
1171  */
1172 function redir_private_images($a, &$item) {
1173
1174         $matches = false;
1175         $cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
1176         if($cnt) {
1177                 //logger("redir_private_images: matches = " . print_r($matches, true));
1178                 foreach($matches as $mtch) {
1179                         if(strpos($mtch[1], '/redir') !== false)
1180                                 continue;
1181
1182                         if((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
1183                                 //logger("redir_private_images: redir");
1184                                 $img_url = 'redir?f=1&quiet=1&url=' . $mtch[1] . '&conurl=' . $item['author-link'];
1185                                 $item['body'] = str_replace($mtch[0], "[img]".$img_url."[/img]", $item['body']);
1186                         }
1187                 }
1188         }
1189
1190 }}
1191
1192 function put_item_in_cache(&$item, $update = false) {
1193
1194         if (($item["rendered-hash"] != hash("md5", $item["body"])) OR ($item["rendered-hash"] == "") OR
1195                 ($item["rendered-html"] == "") OR get_config("system", "ignore_cache")) {
1196
1197                 // The function "redir_private_images" changes the body.
1198                 // I'm not sure if we should store it permanently, so we save the old value.
1199                 $body = $item["body"];
1200
1201                 $a = get_app();
1202                 redir_private_images($a, $item);
1203
1204                 $item["rendered-html"] = prepare_text($item["body"]);
1205                 $item["rendered-hash"] = hash("md5", $item["body"]);
1206                 $item["body"] = $body;
1207
1208                 if ($update AND ($item["id"] != 0)) {
1209                         q("UPDATE `item` SET `rendered-html` = '%s', `rendered-hash` = '%s' WHERE `id` = %d",
1210                                 dbesc($item["rendered-html"]), dbesc($item["rendered-hash"]), intval($item["id"]));
1211                 }
1212         }
1213 }
1214
1215 // Given an item array, convert the body element from bbcode to html and add smilie icons.
1216 // If attach is true, also add icons for item attachments
1217
1218 if(! function_exists('prepare_body')) {
1219 /**
1220  * Given an item array, convert the body element from bbcode to html and add smilie icons.
1221  * If attach is true, also add icons for item attachments
1222  *
1223  * @param array $item
1224  * @param boolean $attach
1225  * @return string item body html
1226  * @hook prepare_body_init item array before any work
1227  * @hook prepare_body ('item'=>item array, 'html'=>body string) after first bbcode to html
1228  * @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
1229  */
1230 function prepare_body(&$item,$attach = false, $preview = false) {
1231
1232         $a = get_app();
1233         call_hooks('prepare_body_init', $item);
1234
1235         $searchpath = z_root()."/search?tag=";
1236
1237         $tags=array();
1238         $hashtags = array();
1239         $mentions = array();
1240
1241         if (!get_config('system','suppress_tags')) {
1242                 $taglist = q("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` IN (%d, %d) ORDER BY `tid`",
1243                                 intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
1244
1245                 foreach($taglist as $tag) {
1246
1247                         if ($tag["url"] == "")
1248                                 $tag["url"] = $searchpath.strtolower($tag["term"]);
1249
1250                         if ($tag["type"] == TERM_HASHTAG) {
1251                                 $hashtags[] = "#<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1252                                 $prefix = "#";
1253                         } elseif ($tag["type"] == TERM_MENTION) {
1254                                 $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1255                                 $prefix = "@";
1256                         }
1257                         $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1258                 }
1259         }
1260
1261         $item['tags'] = $tags;
1262         $item['hashtags'] = $hashtags;
1263         $item['mentions'] = $mentions;
1264
1265         // Update the cached values if there is no "zrl=..." on the links
1266         $update = (!local_user() and !remote_user() and ($item["uid"] == 0));
1267
1268         // Or update it if the current viewer is the intented viewer
1269         if (($item["uid"] == local_user()) AND ($item["uid"] != 0))
1270                 $update = true;
1271
1272         put_item_in_cache($item, $update);
1273         $s = $item["rendered-html"];
1274
1275         $prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview);
1276         call_hooks('prepare_body', $prep_arr);
1277         $s = $prep_arr['html'];
1278
1279         if(! $attach) {
1280                 // Replace the blockquotes with quotes that are used in mails
1281                 $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
1282                 $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
1283                 return $s;
1284         }
1285
1286         $as = '';
1287         $vhead = false;
1288         $arr = explode('[/attach],',$item['attach']);
1289         if(count($arr)) {
1290                 $as .= '<div class="body-attach">';
1291                 foreach($arr as $r) {
1292                         $matches = false;
1293                         $icon = '';
1294                         $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches, PREG_SET_ORDER);
1295                         if($cnt) {
1296                                 foreach($matches as $mtch) {
1297                                         $mime = $mtch[3];
1298
1299                                         if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN))
1300                                                 $the_url = 'redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
1301                                         else
1302                                                 $the_url = $mtch[1];
1303
1304                                         if(strpos($mime, 'video') !== false) {
1305                                                 if(!$vhead) {
1306                                                         $vhead = true;
1307                                                         $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
1308                                                                 '$baseurl' => z_root(),
1309                                                         ));
1310                                                         $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
1311                                                                 '$baseurl' => z_root(),
1312                                                         ));
1313                                                 }
1314
1315                                                 $id = end(explode('/', $the_url));
1316                                                 $as .= replace_macros(get_markup_template('video_top.tpl'), array(
1317                                                         '$video'        => array(
1318                                                                 'id'       => $id,
1319                                                                 'title'         => t('View Video'),
1320                                                                 'src'           => $the_url,
1321                                                                 'mime'          => $mime,
1322                                                         ),
1323                                                 ));
1324                                         }
1325
1326                                         $filetype = strtolower(substr( $mime, 0, strpos($mime,'/') ));
1327                                         if($filetype) {
1328                                                 $filesubtype = strtolower(substr( $mime, strpos($mime,'/') + 1 ));
1329                                                 $filesubtype = str_replace('.', '-', $filesubtype);
1330                                         }
1331                                         else {
1332                                                 $filetype = 'unkn';
1333                                                 $filesubtype = 'unkn';
1334                                         }
1335
1336                                         $icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>';
1337                                         /*$icontype = strtolower(substr($mtch[3],0,strpos($mtch[3],'/')));
1338                                         switch($icontype) {
1339                                                 case 'video':
1340                                                 case 'audio':
1341                                                 case 'image':
1342                                                 case 'text':
1343                                                         $icon = '<div class="attachtype icon s22 type-' . $icontype . '"></div>';
1344                                                         break;
1345                                                 default:
1346                                                         $icon = '<div class="attachtype icon s22 type-unkn"></div>';
1347                                                         break;
1348                                         }*/
1349
1350                                         $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
1351                                         $title .= ' ' . $mtch[2] . ' ' . t('bytes');
1352
1353                                         $as .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" target="_blank" >' . $icon . '</a>';
1354                                 }
1355                         }
1356                 }
1357                 $as .= '<div class="clear"></div></div>';
1358         }
1359         $s = $s . $as;
1360
1361         // map
1362         if(strpos($s,'<div class="map">') !== false && $item['coord']) {
1363                 $x = generate_map(trim($item['coord']));
1364                 if($x) {
1365                         $s = preg_replace('/\<div class\=\"map\"\>/','$0' . $x,$s);
1366                 }
1367         }
1368
1369
1370         // Look for spoiler
1371         $spoilersearch = '<blockquote class="spoiler">';
1372
1373         // Remove line breaks before the spoiler
1374         while ((strpos($s, "\n".$spoilersearch) !== false))
1375                 $s = str_replace("\n".$spoilersearch, $spoilersearch, $s);
1376         while ((strpos($s, "<br />".$spoilersearch) !== false))
1377                 $s = str_replace("<br />".$spoilersearch, $spoilersearch, $s);
1378
1379         while ((strpos($s, $spoilersearch) !== false)) {
1380
1381                 $pos = strpos($s, $spoilersearch);
1382                 $rnd = random_string(8);
1383                 $spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" class="spoiler-wrap fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1384                                         '<blockquote class="spoiler" id="spoiler-'.$rnd.'" style="display: none;">';
1385                 $s = substr($s, 0, $pos).$spoilerreplace.substr($s, $pos+strlen($spoilersearch));
1386         }
1387
1388         // Look for quote with author
1389         $authorsearch = '<blockquote class="author">';
1390
1391         while ((strpos($s, $authorsearch) !== false)) {
1392
1393                 $pos = strpos($s, $authorsearch);
1394                 $rnd = random_string(8);
1395                 $authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" class="author-wrap fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1396                                         '<blockquote class="author" id="author-'.$rnd.'" style="display: block;">';
1397                 $s = substr($s, 0, $pos).$authorreplace.substr($s, $pos+strlen($authorsearch));
1398         }
1399
1400         // replace friendica image url size with theme preference
1401         if (x($a->theme_info,'item_image_size')){
1402             $ps = $a->theme_info['item_image_size'];
1403
1404             $s = preg_replace('|(<img[^>]+src="[^"]+/photo/[0-9a-f]+)-[0-9]|',"$1-".$ps, $s);
1405         }
1406
1407         $prep_arr = array('item' => $item, 'html' => $s);
1408         call_hooks('prepare_body_final', $prep_arr);
1409
1410         return $prep_arr['html'];
1411 }}
1412
1413
1414 if(! function_exists('prepare_text')) {
1415 /**
1416  * Given a text string, convert from bbcode to html and add smilie icons.
1417  *
1418  * @param string $text
1419  * @return string
1420  */
1421 function prepare_text($text) {
1422
1423         require_once('include/bbcode.php');
1424
1425         if(stristr($text,'[nosmile]'))
1426                 $s = bbcode($text);
1427         else
1428                 $s = Smilies::replace(bbcode($text));
1429
1430         return trim($s);
1431 }}
1432
1433
1434
1435 /**
1436  * return array with details for categories and folders for an item
1437  *
1438  * @param array $item
1439  * @return array
1440  *
1441   * [
1442  *      [ // categories array
1443  *          {
1444  *               'name': 'category name',
1445  *               'removeurl': 'url to remove this category',
1446  *               'first': 'is the first in this array? true/false',
1447  *               'last': 'is the last in this array? true/false',
1448  *           } ,
1449  *           ....
1450  *       ],
1451  *       [ //folders array
1452  *                      {
1453  *               'name': 'folder name',
1454  *               'removeurl': 'url to remove this folder',
1455  *               'first': 'is the first in this array? true/false',
1456  *               'last': 'is the last in this array? true/false',
1457  *           } ,
1458  *           ....
1459  *       ]
1460  *  ]
1461  */
1462 function get_cats_and_terms($item) {
1463
1464         $a = get_app();
1465         $categories = array();
1466         $folders = array();
1467
1468         $matches = false; $first = true;
1469         $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1470         if($cnt) {
1471                 foreach($matches as $mtch) {
1472                         $categories[] = array(
1473                                 'name' => xmlify(file_tag_decode($mtch[1])),
1474                                 'url' =>  "#",
1475                                 'removeurl' => ((local_user() == $item['uid'])?'filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""),
1476                                 'first' => $first,
1477                                 'last' => false
1478                         );
1479                         $first = false;
1480                 }
1481         }
1482         if (count($categories)) $categories[count($categories)-1]['last'] = true;
1483
1484
1485         if(local_user() == $item['uid']) {
1486                 $matches = false; $first = true;
1487                 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1488                 if($cnt) {
1489                         foreach($matches as $mtch) {
1490                                 $folders[] = array(
1491                                         'name' => xmlify(file_tag_decode($mtch[1])),
1492                                         'url' =>  "#",
1493                                         'removeurl' => ((local_user() == $item['uid'])?'filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""),
1494                                         'first' => $first,
1495                                         'last' => false
1496                                 );
1497                                 $first = false;
1498                         }
1499                 }
1500         }
1501
1502         if (count($folders)) $folders[count($folders)-1]['last'] = true;
1503
1504         return array($categories, $folders);
1505 }
1506
1507 if(! function_exists('get_plink')) {
1508 /**
1509  * get private link for item
1510  * @param array $item
1511  * @return boolean|array False if item has not plink, otherwise array('href'=>plink url, 'title'=>translated title)
1512  */
1513 function get_plink($item) {
1514         $a = get_app();
1515
1516         if ($a->user['nickname'] != "") {
1517                 $ret = array(
1518                                 //'href' => "display/".$a->user['nickname']."/".$item['id'],
1519                                 'href' => "display/".$item['guid'],
1520                                 'orig' => "display/".$item['guid'],
1521                                 'title' => t('View on separate page'),
1522                                 'orig_title' => t('view on separate page'),
1523                         );
1524
1525                 if (x($item,'plink')) {
1526                         $ret["href"] = $a->remove_baseurl($item['plink']);
1527                         $ret["title"] = t('link to source');
1528                 }
1529
1530         } elseif (x($item,'plink') && ($item['private'] != 1))
1531                 $ret = array(
1532                                 'href' => $item['plink'],
1533                                 'orig' => $item['plink'],
1534                                 'title' => t('link to source'),
1535                         );
1536         else
1537                 $ret = array();
1538
1539         //if (x($item,'plink') && ($item['private'] != 1))
1540
1541         return($ret);
1542 }}
1543
1544 if(! function_exists('unamp')) {
1545 /**
1546  * replace html amp entity with amp char
1547  * @param string $s
1548  * @return string
1549  */
1550 function unamp($s) {
1551         return str_replace('&amp;', '&', $s);
1552 }}
1553
1554
1555 if(! function_exists('return_bytes')) {
1556 /**
1557  * return number of bytes in size (K, M, G)
1558  * @param string $size_str
1559  * @return number
1560  */
1561 function return_bytes ($size_str) {
1562         switch (substr ($size_str, -1)) {
1563                 case 'M': case 'm': return (int)$size_str * 1048576;
1564                 case 'K': case 'k': return (int)$size_str * 1024;
1565                 case 'G': case 'g': return (int)$size_str * 1073741824;
1566                 default: return $size_str;
1567         }
1568 }}
1569
1570 /**
1571  * @return string
1572  */
1573 function generate_user_guid() {
1574         $found = true;
1575         do {
1576                 $guid = get_guid(32);
1577                 $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
1578                         dbesc($guid)
1579                 );
1580                 if(! count($x))
1581                         $found = false;
1582         } while ($found == true );
1583         return $guid;
1584 }
1585
1586
1587 /**
1588  * @param string $s
1589  * @param boolean $strip_padding
1590  * @return string
1591  */
1592 function base64url_encode($s, $strip_padding = false) {
1593
1594         $s = strtr(base64_encode($s),'+/','-_');
1595
1596         if($strip_padding)
1597                 $s = str_replace('=','',$s);
1598
1599         return $s;
1600 }
1601
1602 /**
1603  * @param string $s
1604  * @return string
1605  */
1606 function base64url_decode($s) {
1607
1608         if(is_array($s)) {
1609                 logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
1610                 return $s;
1611         }
1612
1613 /*
1614  *  // Placeholder for new rev of salmon which strips base64 padding.
1615  *  // PHP base64_decode handles the un-padded input without requiring this step
1616  *  // Uncomment if you find you need it.
1617  *
1618  *      $l = strlen($s);
1619  *      if(! strpos($s,'=')) {
1620  *              $m = $l % 4;
1621  *              if($m == 2)
1622  *                      $s .= '==';
1623  *              if($m == 3)
1624  *                      $s .= '=';
1625  *      }
1626  *
1627  */
1628
1629         return base64_decode(strtr($s,'-_','+/'));
1630 }
1631
1632
1633 if (!function_exists('str_getcsv')) {
1634         /**
1635          * Parse csv string
1636          *
1637          * @param string $input
1638          * @param string $delimiter
1639          * @param string $enclosure
1640          * @param string $escape
1641          * @param string $eol
1642          * @return boolean|array False on error, otherwise array[row][column]
1643          */
1644 function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
1645         if (is_string($input) && !empty($input)) {
1646                 $output = array();
1647                 $tmp    = preg_split("/".$eol."/",$input);
1648                 if (is_array($tmp) && !empty($tmp)) {
1649                         while (list($line_num, $line) = each($tmp)) {
1650                                 if (preg_match("/".$escape.$enclosure."/",$line)) {
1651                                         while ($strlen = strlen($line)) {
1652                                                 $pos_delimiter       = strpos($line,$delimiter);
1653                                                 $pos_enclosure_start = strpos($line,$enclosure);
1654                                                 if (
1655                                                         is_int($pos_delimiter) && is_int($pos_enclosure_start)
1656                                                         && ($pos_enclosure_start < $pos_delimiter)
1657                                                         ) {
1658                                                         $enclosed_str = substr($line,1);
1659                                                         $pos_enclosure_end = strpos($enclosed_str,$enclosure);
1660                                                         $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
1661                                                         $output[$line_num][] = $enclosed_str;
1662                                                         $offset = $pos_enclosure_end+3;
1663                                                 } else {
1664                                                         if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
1665                                                                 $output[$line_num][] = substr($line,0);
1666                                                                 $offset = strlen($line);
1667                                                         } else {
1668                                                                 $output[$line_num][] = substr($line,0,$pos_delimiter);
1669                                                                 $offset = (
1670                                                                         !empty($pos_enclosure_start)
1671                                                                         && ($pos_enclosure_start < $pos_delimiter)
1672                                                                         )
1673                                                                         ?$pos_enclosure_start
1674                                                                         :$pos_delimiter+1;
1675                                                         }
1676                                                 }
1677                                                 $line = substr($line,$offset);
1678                                         }
1679                                 } else {
1680                                         $line = preg_split("/".$delimiter."/",$line);
1681
1682                                         /*
1683                                          * Validating against pesky extra line breaks creating false rows.
1684                                          */
1685                                         if (is_array($line) && !empty($line[0])) {
1686                                                 $output[$line_num] = $line;
1687                                 }
1688                                 }
1689                         }
1690                         return $output;
1691                 } else {
1692                 return false;
1693                 }
1694         } else {
1695                 return false;
1696         }
1697 }
1698 }
1699
1700 /**
1701  * return div element with class 'clear'
1702  * @return string
1703  * @deprecated
1704  */
1705 function cleardiv() {
1706         return '<div class="clear"></div>';
1707 }
1708
1709
1710 function bb_translate_video($s) {
1711
1712         $matches = null;
1713         $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
1714         if($r) {
1715                 foreach($matches as $mtch) {
1716                         if((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
1717                                 $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
1718                         elseif(stristr($mtch[1],'vimeo'))
1719                                 $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
1720                 }
1721         }
1722         return $s;
1723 }
1724
1725 function html2bb_video($s) {
1726
1727         $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
1728                         '[youtube]$2[/youtube]', $s);
1729
1730         $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
1731                         '[youtube]$2[/youtube]', $s);
1732
1733         $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
1734                         '[vimeo]$2[/vimeo]', $s);
1735
1736         return $s;
1737 }
1738
1739 /**
1740  * apply xmlify() to all values of array $val, recursively
1741  * @param array $val
1742  * @return array
1743  */
1744 function array_xmlify($val){
1745         if (is_bool($val)) return $val?"true":"false";
1746         if (is_array($val)) return array_map('array_xmlify', $val);
1747         return xmlify((string) $val);
1748 }
1749
1750
1751 /**
1752  * transorm link href and img src from relative to absolute
1753  *
1754  * @param string $text
1755  * @param string $base base url
1756  * @return string
1757  */
1758 function reltoabs($text, $base) {
1759         if (empty($base))
1760             return $text;
1761
1762         $base = rtrim($base,'/');
1763
1764         $base2 = $base . "/";
1765
1766         // Replace links
1767         $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
1768         $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
1769         $text = preg_replace($pattern, $replace, $text);
1770
1771         $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
1772         $replace = "<a\${1} href=\"" . $base . "\${2}\"";
1773         $text = preg_replace($pattern, $replace, $text);
1774
1775         // Replace images
1776         $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
1777         $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
1778         $text = preg_replace($pattern, $replace, $text);
1779
1780         $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
1781         $replace = "<img\${1} src=\"" . $base . "\${2}\"";
1782         $text = preg_replace($pattern, $replace, $text);
1783
1784
1785         // Done
1786         return $text;
1787 }
1788
1789 /**
1790  * get translated item type
1791  *
1792  * @param array $itme
1793  * @return string
1794  */
1795 function item_post_type($item) {
1796         if(intval($item['event-id']))
1797                 return t('event');
1798         if(strlen($item['resource-id']))
1799                 return t('photo');
1800         if(strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST)
1801                 return t('activity');
1802         if($item['id'] != $item['parent'])
1803                 return t('comment');
1804         return t('post');
1805 }
1806
1807 // post categories and "save to file" use the same item.file table for storage.
1808 // We will differentiate the different uses by wrapping categories in angle brackets
1809 // and save to file categories in square brackets.
1810 // To do this we need to escape these characters if they appear in our tag.
1811
1812 function file_tag_encode($s) {
1813         return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s);
1814 }
1815
1816 function file_tag_decode($s) {
1817         return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s);
1818 }
1819
1820 function file_tag_file_query($table,$s,$type = 'file') {
1821
1822         if($type == 'file')
1823                 $str = preg_quote( '[' . str_replace('%','%%',file_tag_encode($s)) . ']' );
1824         else
1825                 $str = preg_quote( '<' . str_replace('%','%%',file_tag_encode($s)) . '>' );
1826         return " AND " . (($table) ? dbesc($table) . '.' : '') . "file regexp '" . dbesc($str) . "' ";
1827 }
1828
1829 // ex. given music,video return <music><video> or [music][video]
1830 function file_tag_list_to_file($list,$type = 'file') {
1831         $tag_list = '';
1832         if(strlen($list)) {
1833                 $list_array = explode(",",$list);
1834                 if($type == 'file') {
1835                         $lbracket = '[';
1836                         $rbracket = ']';
1837                 }
1838                 else {
1839                         $lbracket = '<';
1840                         $rbracket = '>';
1841                 }
1842
1843                 foreach($list_array as $item) {
1844                   if(strlen($item)) {
1845                                 $tag_list .= $lbracket . file_tag_encode(trim($item))  . $rbracket;
1846                         }
1847                 }
1848         }
1849         return $tag_list;
1850 }
1851
1852 // ex. given <music><video>[friends], return music,video or friends
1853 function file_tag_file_to_list($file,$type = 'file') {
1854         $matches = false;
1855         $list = '';
1856         if($type == 'file') {
1857                 $cnt = preg_match_all('/\[(.*?)\]/',$file,$matches,PREG_SET_ORDER);
1858         }
1859         else {
1860                 $cnt = preg_match_all('/<(.*?)>/',$file,$matches,PREG_SET_ORDER);
1861         }
1862         if($cnt) {
1863                 foreach($matches as $mtch) {
1864                         if(strlen($list))
1865                                 $list .= ',';
1866                         $list .= file_tag_decode($mtch[1]);
1867                 }
1868         }
1869
1870         return $list;
1871 }
1872
1873 function file_tag_update_pconfig($uid,$file_old,$file_new,$type = 'file') {
1874         // $file_old - categories previously associated with an item
1875         // $file_new - new list of categories for an item
1876
1877         if(! intval($uid))
1878                 return false;
1879
1880         if($file_old == $file_new)
1881                 return true;
1882
1883         $saved = get_pconfig($uid,'system','filetags');
1884         if(strlen($saved)) {
1885                 if($type == 'file') {
1886                         $lbracket = '[';
1887                         $rbracket = ']';
1888                         $termtype = TERM_FILE;
1889                 }
1890                 else {
1891                         $lbracket = '<';
1892                         $rbracket = '>';
1893                         $termtype = TERM_CATEGORY;
1894                 }
1895
1896                 $filetags_updated = $saved;
1897
1898                 // check for new tags to be added as filetags in pconfig
1899                 $new_tags = array();
1900                 $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
1901
1902                 foreach($check_new_tags as $tag) {
1903                         if(! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket))
1904                                 $new_tags[] = $tag;
1905                 }
1906
1907                 $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type);
1908
1909                 // check for deleted tags to be removed from filetags in pconfig
1910                 $deleted_tags = array();
1911                 $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
1912
1913                 foreach($check_deleted_tags as $tag) {
1914                         if(! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket))
1915                                 $deleted_tags[] = $tag;
1916                 }
1917
1918                 foreach($deleted_tags as $key => $tag) {
1919                         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
1920                                 dbesc($tag),
1921                                 intval(TERM_OBJ_POST),
1922                                 intval($termtype),
1923                                 intval($uid));
1924
1925                         //$r = q("select file from item where uid = %d " . file_tag_file_query('item',$tag,$type),
1926                         //      intval($uid)
1927                         //);
1928
1929                         if(count($r)) {
1930                                 unset($deleted_tags[$key]);
1931                         }
1932                         else {
1933                                 $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
1934                         }
1935                 }
1936
1937                 if($saved != $filetags_updated) {
1938                         set_pconfig($uid,'system','filetags', $filetags_updated);
1939                 }
1940                 return true;
1941         }
1942         else
1943                 if(strlen($file_new)) {
1944                         set_pconfig($uid,'system','filetags', $file_new);
1945                 }
1946                 return true;
1947 }
1948
1949 function file_tag_save_file($uid,$item,$file) {
1950         require_once("include/files.php");
1951
1952         $result = false;
1953         if(! intval($uid))
1954                 return false;
1955         $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1956                 intval($item),
1957                 intval($uid)
1958         );
1959         if(count($r)) {
1960                 if(! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']'))
1961                         q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
1962                                 dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
1963                                 intval($item),
1964                                 intval($uid)
1965                         );
1966
1967                 create_files_from_item($item);
1968
1969                 $saved = get_pconfig($uid,'system','filetags');
1970                 if((! strlen($saved)) || (! stristr($saved,'[' . file_tag_encode($file) . ']')))
1971                         set_pconfig($uid,'system','filetags',$saved . '[' . file_tag_encode($file) . ']');
1972                 info( t('Item filed') );
1973         }
1974         return true;
1975 }
1976
1977 function file_tag_unsave_file($uid,$item,$file,$cat = false) {
1978         require_once("include/files.php");
1979
1980         $result = false;
1981         if(! intval($uid))
1982                 return false;
1983
1984         if($cat == true) {
1985                 $pattern = '<' . file_tag_encode($file) . '>' ;
1986                 $termtype = TERM_CATEGORY;
1987         } else {
1988                 $pattern = '[' . file_tag_encode($file) . ']' ;
1989                 $termtype = TERM_FILE;
1990         }
1991
1992
1993         $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1994                 intval($item),
1995                 intval($uid)
1996         );
1997         if(! count($r))
1998                 return false;
1999
2000         q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
2001                 dbesc(str_replace($pattern,'',$r[0]['file'])),
2002                 intval($item),
2003                 intval($uid)
2004         );
2005
2006         create_files_from_item($item);
2007
2008         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2009                 dbesc($file),
2010                 intval(TERM_OBJ_POST),
2011                 intval($termtype),
2012                 intval($uid));
2013
2014         //$r = q("select file from item where uid = %d and deleted = 0 " . file_tag_file_query('item',$file,(($cat) ? 'category' : 'file')),
2015         //);
2016
2017         if(! count($r)) {
2018                 $saved = get_pconfig($uid,'system','filetags');
2019                 set_pconfig($uid,'system','filetags',str_replace($pattern,'',$saved));
2020
2021         }
2022         return true;
2023 }
2024
2025 function normalise_openid($s) {
2026         return trim(str_replace(array('http://','https://'),array('',''),$s),'/');
2027 }
2028
2029
2030 function undo_post_tagging($s) {
2031         $matches = null;
2032         $cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism',$s,$matches,PREG_SET_ORDER);
2033         if($cnt) {
2034                 foreach($matches as $mtch) {
2035                         $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
2036                 }
2037         }
2038         return $s;
2039 }
2040
2041 function fix_mce_lf($s) {
2042         $s = str_replace("\r\n","\n",$s);
2043 //      $s = str_replace("\n\n","\n",$s);
2044         return $s;
2045 }
2046
2047
2048 function protect_sprintf($s) {
2049         return(str_replace('%','%%',$s));
2050 }
2051
2052
2053 function is_a_date_arg($s) {
2054         $i = intval($s);
2055         if($i > 1900) {
2056                 $y = date('Y');
2057                 if($i <= $y+1 && strpos($s,'-') == 4) {
2058                         $m = intval(substr($s,5));
2059                         if($m > 0 && $m <= 12)
2060                                 return true;
2061                 }
2062         }
2063         return false;
2064 }
2065
2066 /**
2067  * remove intentation from a text
2068  */
2069 function deindent($text, $chr="[\t ]", $count=NULL) {
2070         $text = fix_mce_lf($text);
2071         $lines = explode("\n", $text);
2072         if (is_null($count)) {
2073                 $m = array();
2074                 $k=0; while($k<count($lines) && strlen($lines[$k])==0) $k++;
2075                 preg_match("|^".$chr."*|", $lines[$k], $m);
2076                 $count = strlen($m[0]);
2077         }
2078         for ($k=0; $k<count($lines); $k++){
2079                 $lines[$k] = preg_replace("|^".$chr."{".$count."}|", "", $lines[$k]);
2080         }
2081
2082         return implode("\n", $lines);
2083 }
2084
2085 function formatBytes($bytes, $precision = 2) {
2086          $units = array('B', 'KB', 'MB', 'GB', 'TB');
2087
2088         $bytes = max($bytes, 0);
2089         $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
2090         $pow = min($pow, count($units) - 1);
2091
2092         $bytes /= pow(1024, $pow);
2093
2094         return round($bytes, $precision) . ' ' . $units[$pow];
2095 }
2096
2097 /**
2098  * @brief translate and format the networkname of a contact
2099  *
2100  * @param string $network
2101  *      Networkname of the contact (e.g. dfrn, rss and so on)
2102  * @param sting $url
2103  *      The contact url
2104  * @return string
2105  */
2106 function format_network_name($network, $url = 0) {
2107         if ($network != "") {
2108                 require_once('include/contact_selectors.php');
2109                 if ($url != "")
2110                         $network_name = '<a href="'.$url.'">'.network_to_name($network, $url)."</a>";
2111                 else
2112                         $network_name = network_to_name($network);
2113
2114                 return $network_name;
2115         }
2116
2117 }
2118
2119 /**
2120  * @brief Syntax based code highlighting for popular languages.
2121  * @param string $s Code block
2122  * @param string $lang Programming language
2123  * @return string Formated html
2124  */
2125 function text_highlight($s,$lang) {
2126         if($lang === 'js')
2127                 $lang = 'javascript';
2128
2129         if(! strpos('Text_Highlighter',get_include_path())) {
2130                 set_include_path(get_include_path() . PATH_SEPARATOR . 'library/Text_Highlighter');
2131         }
2132
2133         require_once('library/Text_Highlighter/Text/Highlighter.php');
2134         require_once('library/Text_Highlighter/Text/Highlighter/Renderer/Html.php');
2135         $options = array(
2136                 'numbers' => HL_NUMBERS_LI,
2137                 'tabsize' => 4,
2138                 );
2139
2140         $tag_added = false;
2141         $s = trim(html_entity_decode($s,ENT_COMPAT));
2142         $s = str_replace("    ","\t",$s);
2143
2144         // The highlighter library insists on an opening php tag for php code blocks. If
2145         // it isn't present, nothing is highlighted. So we're going to see if it's present.
2146         // If not, we'll add it, and then quietly remove it after we get the processed output back.
2147
2148         if($lang === 'php') {
2149                 if(strpos('<?php',$s) !== 0) {
2150                         $s = '<?php' . "\n" . $s;
2151                         $tag_added = true;
2152                 }
2153         }
2154
2155         $renderer = new Text_Highlighter_Renderer_HTML($options);
2156         $hl = Text_Highlighter::factory($lang);
2157         $hl->setRenderer($renderer);
2158         $o = $hl->highlight($s);
2159         $o = str_replace(["    ","\n"],["&nbsp;&nbsp;&nbsp;&nbsp;",''],$o);
2160
2161         if($tag_added) {
2162                 $b = substr($o,0,strpos($o,'<li>'));
2163                 $e = substr($o,strpos($o,'</li>'));
2164                 $o = $b . $e;
2165         }
2166
2167         return('<code>' . $o . '</code>');
2168 }