Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2311 lines
63 KiB

8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
9 years ago
9 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
10 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
8 years ago
9 years ago
9 years ago
8 years ago
8 years ago
9 years ago
10 years ago
10 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
10 years ago
8 years ago
8 years ago
10 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
  1. <?php
  2. require_once("include/template_processor.php");
  3. require_once("include/friendica_smarty.php");
  4. require_once("include/map.php");
  5. require_once("mod/proxy.php");
  6. if(! function_exists('replace_macros')) {
  7. /**
  8. * This is our template processor
  9. *
  10. * @param string|FriendicaSmarty $s the string requiring macro substitution,
  11. * or an instance of FriendicaSmarty
  12. * @param array $r key value pairs (search => replace)
  13. * @return string substituted string
  14. */
  15. function replace_macros($s,$r) {
  16. $stamp1 = microtime(true);
  17. $a = get_app();
  18. $t = $a->template_engine();
  19. try {
  20. $output = $t->replace_macros($s,$r);
  21. } catch (Exception $e) {
  22. echo "<pre><b>".__function__."</b>: ".$e->getMessage()."</pre>"; killme();
  23. }
  24. $a->save_timestamp($stamp1, "rendering");
  25. return $output;
  26. }}
  27. // random string, there are 86 characters max in text mode, 128 for hex
  28. // output is urlsafe
  29. define('RANDOM_STRING_HEX', 0x00 );
  30. define('RANDOM_STRING_TEXT', 0x01 );
  31. if(! function_exists('random_string')) {
  32. function random_string($size = 64,$type = RANDOM_STRING_HEX) {
  33. // generate a bit of entropy and run it through the whirlpool
  34. $s = hash('whirlpool', (string) rand() . uniqid(rand(),true) . (string) rand(),(($type == RANDOM_STRING_TEXT) ? true : false));
  35. $s = (($type == RANDOM_STRING_TEXT) ? str_replace("\n","",base64url_encode($s,true)) : $s);
  36. return(substr($s,0,$size));
  37. }}
  38. if(! function_exists('notags')) {
  39. /**
  40. * This is our primary input filter.
  41. *
  42. * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
  43. * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
  44. * after cleansing, and angle chars with the high bit set could get through as markup.
  45. *
  46. * This is now disabled because it was interfering with some legitimate unicode sequences
  47. * and hopefully there aren't a lot of those browsers left.
  48. *
  49. * Use this on any text input where angle chars are not valid or permitted
  50. * They will be replaced with safer brackets. This may be filtered further
  51. * if these are not allowed either.
  52. *
  53. * @param string $string Input string
  54. * @return string Filtered string
  55. */
  56. function notags($string) {
  57. return(str_replace(array("<",">"), array('[',']'), $string));
  58. // High-bit filter no longer used
  59. // return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
  60. }}
  61. if(! function_exists('escape_tags')) {
  62. /**
  63. * use this on "body" or "content" input where angle chars shouldn't be removed,
  64. * and allow them to be safely displayed.
  65. * @param string $string
  66. * @return string
  67. */
  68. function escape_tags($string) {
  69. return(htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false));
  70. }}
  71. // generate a string that's random, but usually pronounceable.
  72. // used to generate initial passwords
  73. if(! function_exists('autoname')) {
  74. /**
  75. * generate a string that's random, but usually pronounceable.
  76. * used to generate initial passwords
  77. * @param int $len
  78. * @return string
  79. */
  80. function autoname($len) {
  81. if($len <= 0)
  82. return '';
  83. $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u');
  84. if(mt_rand(0,5) == 4)
  85. $vowels[] = 'y';
  86. $cons = array(
  87. 'b','bl','br',
  88. 'c','ch','cl','cr',
  89. 'd','dr',
  90. 'f','fl','fr',
  91. 'g','gh','gl','gr',
  92. 'h',
  93. 'j',
  94. 'k','kh','kl','kr',
  95. 'l',
  96. 'm',
  97. 'n',
  98. 'p','ph','pl','pr',
  99. 'qu',
  100. 'r','rh',
  101. 's','sc','sh','sm','sp','st',
  102. 't','th','tr',
  103. 'v',
  104. 'w','wh',
  105. 'x',
  106. 'z','zh'
  107. );
  108. $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
  109. 'nd','ng','nk','nt','rn','rp','rt');
  110. $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
  111. 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
  112. $start = mt_rand(0,2);
  113. if($start == 0)
  114. $table = $vowels;
  115. else
  116. $table = $cons;
  117. $word = '';
  118. for ($x = 0; $x < $len; $x ++) {
  119. $r = mt_rand(0,count($table) - 1);
  120. $word .= $table[$r];
  121. if($table == $vowels)
  122. $table = array_merge($cons,$midcons);
  123. else
  124. $table = $vowels;
  125. }
  126. $word = substr($word,0,$len);
  127. foreach($noend as $noe) {
  128. if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
  129. $word = substr($word,0,-1);
  130. break;
  131. }
  132. }
  133. if(substr($word,-1) == 'q')
  134. $word = substr($word,0,-1);
  135. return $word;
  136. }}
  137. // escape text ($str) for XML transport
  138. // returns escaped text.
  139. if(! function_exists('xmlify')) {
  140. /**
  141. * escape text ($str) for XML transport
  142. * @param string $str
  143. * @return string Escaped text.
  144. */
  145. function xmlify($str) {
  146. /* $buffer = '';
  147. $len = mb_strlen($str);
  148. for($x = 0; $x < $len; $x ++) {
  149. $char = mb_substr($str,$x,1);
  150. switch( $char ) {
  151. case "\r" :
  152. break;
  153. case "&" :
  154. $buffer .= '&amp;';
  155. break;
  156. case "'" :
  157. $buffer .= '&apos;';
  158. break;
  159. case "\"" :
  160. $buffer .= '&quot;';
  161. break;
  162. case '<' :
  163. $buffer .= '&lt;';
  164. break;
  165. case '>' :
  166. $buffer .= '&gt;';
  167. break;
  168. case "\n" :
  169. $buffer .= "\n";
  170. break;
  171. default :
  172. $buffer .= $char;
  173. break;
  174. }
  175. }*/
  176. /*
  177. $buffer = mb_ereg_replace("&", "&amp;", $str);
  178. $buffer = mb_ereg_replace("'", "&apos;", $buffer);
  179. $buffer = mb_ereg_replace('"', "&quot;", $buffer);
  180. $buffer = mb_ereg_replace("<", "&lt;", $buffer);
  181. $buffer = mb_ereg_replace(">", "&gt;", $buffer);
  182. */
  183. $buffer = htmlspecialchars($str, ENT_QUOTES, "UTF-8");
  184. $buffer = trim($buffer);
  185. return($buffer);
  186. }}
  187. if(! function_exists('unxmlify')) {
  188. /**
  189. * undo an xmlify
  190. * @param string $s xml escaped text
  191. * @return string unescaped text
  192. */
  193. function unxmlify($s) {
  194. // $ret = str_replace('&amp;','&', $s);
  195. // $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
  196. /*$ret = mb_ereg_replace('&amp;', '&', $s);
  197. $ret = mb_ereg_replace('&apos;', "'", $ret);
  198. $ret = mb_ereg_replace('&quot;', '"', $ret);
  199. $ret = mb_ereg_replace('&lt;', "<", $ret);
  200. $ret = mb_ereg_replace('&gt;', ">", $ret);
  201. */
  202. $ret = htmlspecialchars_decode($s, ENT_QUOTES);
  203. return $ret;
  204. }}
  205. if(! function_exists('hex2bin')) {
  206. /**
  207. * convenience wrapper, reverse the operation "bin2hex"
  208. * @param string $s
  209. * @return number
  210. */
  211. function hex2bin($s) {
  212. if(! (is_string($s) && strlen($s)))
  213. return '';
  214. if(! ctype_xdigit($s)) {
  215. return($s);
  216. }
  217. return(pack("H*",$s));
  218. }}
  219. if(! function_exists('paginate_data')) {
  220. /**
  221. * Automatica pagination data.
  222. *
  223. * @param App $a App instance
  224. * @param int $count [optional] item count (used with alt pager)
  225. * @return Array data for pagination template
  226. */
  227. function paginate_data(&$a, $count=null) {
  228. $stripped = preg_replace('/([&?]page=[0-9]*)/','',$a->query_string);
  229. $stripped = str_replace('q=','',$stripped);
  230. $stripped = trim($stripped,'/');
  231. $pagenum = $a->pager['page'];
  232. if (($a->page_offset != "") AND !preg_match('/[?&].offset=/', $stripped))
  233. $stripped .= "&offset=".urlencode($a->page_offset);
  234. $url = $a->get_baseurl() . '/' . $stripped;
  235. $data = array();
  236. function _l(&$d, $name, $url, $text, $class="") {
  237. if (!strpos($url, "?")) {
  238. if ($pos = strpos($url, "&"))
  239. $url = substr($url, 0, $pos)."?".substr($url, $pos + 1);
  240. }
  241. $d[$name] = array('url'=>$url, 'text'=>$text, 'class'=>$class);
  242. }
  243. if (!is_null($count)){
  244. // alt pager
  245. if($a->pager['page']>1)
  246. _l($data, "prev", $url.'&page='.($a->pager['page'] - 1), t('newer'));
  247. if($count>0)
  248. _l($data, "next", $url.'&page='.($a->pager['page'] + 1), t('older'));
  249. } else {
  250. // full pager
  251. if($a->pager['total'] > $a->pager['itemspage']) {
  252. if($a->pager['page'] != 1)
  253. _l($data, "prev", $url.'&page='.($a->pager['page'] - 1), t('prev'));
  254. _l($data, "first", $url."&page=1", t('first'));
  255. $numpages = $a->pager['total'] / $a->pager['itemspage'];
  256. $numstart = 1;
  257. $numstop = $numpages;
  258. if($numpages > 14) {
  259. $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
  260. $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
  261. }
  262. $pages = array();
  263. for($i = $numstart; $i <= $numstop; $i++){
  264. if($i == $a->pager['page'])
  265. _l($pages, $i, "#", $i, "current");
  266. else
  267. _l($pages, $i, $url."&page=$i", $i, "n");
  268. }
  269. if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
  270. if($i == $a->pager['page'])
  271. _l($pages, $i, "#", $i, "current");
  272. else
  273. _l($pages, $i, $url."&page=$i", $i, "n");
  274. }
  275. $data['pages'] = $pages;
  276. $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
  277. _l($data, "last", $url."&page=$lastpage", t('last'));
  278. if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
  279. _l($data, "next", $url."&page=".($a->pager['page'] + 1), t('next'));
  280. }
  281. }
  282. return $data;
  283. }}
  284. if(! function_exists('paginate')) {
  285. /**
  286. * Automatic pagination.
  287. *
  288. * To use, get the count of total items.
  289. * Then call $a->set_pager_total($number_items);
  290. * Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
  291. * Then call paginate($a) after the end of the display loop to insert the pager block on the page
  292. * (assuming there are enough items to paginate).
  293. * When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
  294. * will limit the results to the correct items for the current page.
  295. * The actual page handling is then accomplished at the application layer.
  296. *
  297. * @param App $a App instance
  298. * @return string html for pagination #FIXME remove html
  299. */
  300. function paginate(&$a) {
  301. $data = paginate_data($a);
  302. $tpl = get_markup_template("paginate.tpl");
  303. return replace_macros($tpl, array("pager" => $data));
  304. }}
  305. if(! function_exists('alt_pager')) {
  306. /**
  307. * Alternative pager
  308. * @param App $a App instance
  309. * @param int $i
  310. * @return string html for pagination #FIXME remove html
  311. */
  312. function alt_pager(&$a, $i) {
  313. $data = paginate_data($a, $i);
  314. $tpl = get_markup_template("paginate.tpl");
  315. return replace_macros($tpl, array('pager' => $data));
  316. }}
  317. if(! function_exists('scroll_loader')) {
  318. /**
  319. * Loader for infinite scrolling
  320. * @return string html for loader
  321. */
  322. function scroll_loader() {
  323. $tpl = get_markup_template("scroll_loader.tpl");
  324. return replace_macros($tpl, array(
  325. 'wait' => t('Loading more entries...'),
  326. 'end' => t('The end')
  327. ));
  328. }}
  329. if(! function_exists('expand_acl')) {
  330. /**
  331. * Turn user/group ACLs stored as angle bracketed text into arrays
  332. *
  333. * @param string $s
  334. * @return array
  335. */
  336. function expand_acl($s) {
  337. // turn string array of angle-bracketed elements into numeric array
  338. // e.g. "<1><2><3>" => array(1,2,3);
  339. $ret = array();
  340. if(strlen($s)) {
  341. $t = str_replace('<','',$s);
  342. $a = explode('>',$t);
  343. foreach($a as $aa) {
  344. if(intval($aa))
  345. $ret[] = intval($aa);
  346. }
  347. }
  348. return $ret;
  349. }}
  350. if(! function_exists('sanitise_acl')) {
  351. /**
  352. * Wrap ACL elements in angle brackets for storage
  353. * @param string $item
  354. */
  355. function sanitise_acl(&$item) {
  356. if(intval($item))
  357. $item = '<' . intval(notags(trim($item))) . '>';
  358. else
  359. unset($item);
  360. }}
  361. if(! function_exists('perms2str')) {
  362. /**
  363. * Convert an ACL array to a storable string
  364. *
  365. * Normally ACL permissions will be an array.
  366. * We'll also allow a comma-separated string.
  367. *
  368. * @param string|array $p
  369. * @return string
  370. */
  371. function perms2str($p) {
  372. $ret = '';
  373. if(is_array($p))
  374. $tmp = $p;
  375. else
  376. $tmp = explode(',',$p);
  377. if(is_array($tmp)) {
  378. array_walk($tmp,'sanitise_acl');
  379. $ret = implode('',$tmp);
  380. }
  381. return $ret;
  382. }}
  383. if(! function_exists('item_new_uri')) {
  384. /**
  385. * generate a guaranteed unique (for this domain) item ID for ATOM
  386. * safe from birthday paradox
  387. *
  388. * @param string $hostname
  389. * @param int $uid
  390. * @return string
  391. */
  392. function item_new_uri($hostname,$uid) {
  393. do {
  394. $dups = false;
  395. $hash = random_string();
  396. $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
  397. $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
  398. dbesc($uri));
  399. if(count($r))
  400. $dups = true;
  401. } while($dups == true);
  402. return $uri;
  403. }}
  404. // Generate a guaranteed unique photo ID.
  405. // safe from birthday paradox
  406. if(! function_exists('photo_new_resource')) {
  407. /**
  408. * Generate a guaranteed unique photo ID.
  409. * safe from birthday paradox
  410. *
  411. * @return string
  412. */
  413. function photo_new_resource() {
  414. do {
  415. $found = false;
  416. $resource = hash('md5',uniqid(mt_rand(),true));
  417. $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
  418. dbesc($resource)
  419. );
  420. if(count($r))
  421. $found = true;
  422. } while($found == true);
  423. return $resource;
  424. }}
  425. if(! function_exists('load_view_file')) {
  426. /**
  427. * @deprecated
  428. * wrapper to load a view template, checking for alternate
  429. * languages before falling back to the default
  430. *
  431. * @global string $lang
  432. * @global App $a
  433. * @param string $s view name
  434. * @return string
  435. */
  436. function load_view_file($s) {
  437. global $lang, $a;
  438. if(! isset($lang))
  439. $lang = 'en';
  440. $b = basename($s);
  441. $d = dirname($s);
  442. if(file_exists("$d/$lang/$b")) {
  443. $stamp1 = microtime(true);
  444. $content = file_get_contents("$d/$lang/$b");
  445. $a->save_timestamp($stamp1, "file");
  446. return $content;
  447. }
  448. $theme = current_theme();
  449. if(file_exists("$d/theme/$theme/$b")) {
  450. $stamp1 = microtime(true);
  451. $content = file_get_contents("$d/theme/$theme/$b");
  452. $a->save_timestamp($stamp1, "file");
  453. return $content;
  454. }
  455. $stamp1 = microtime(true);
  456. $content = file_get_contents($s);
  457. $a->save_timestamp($stamp1, "file");
  458. return $content;
  459. }}
  460. if(! function_exists('get_intltext_template')) {
  461. /**
  462. * load a view template, checking for alternate
  463. * languages before falling back to the default
  464. *
  465. * @global string $lang
  466. * @param string $s view path
  467. * @return string
  468. */
  469. function get_intltext_template($s) {
  470. global $lang;
  471. $a = get_app();
  472. $engine = '';
  473. if($a->theme['template_engine'] === 'smarty3')
  474. $engine = "/smarty3";
  475. if(! isset($lang))
  476. $lang = 'en';
  477. if(file_exists("view/$lang$engine/$s")) {
  478. $stamp1 = microtime(true);
  479. $content = file_get_contents("view/$lang$engine/$s");
  480. $a->save_timestamp($stamp1, "file");
  481. return $content;
  482. } elseif(file_exists("view/en$engine/$s")) {
  483. $stamp1 = microtime(true);
  484. $content = file_get_contents("view/en$engine/$s");
  485. $a->save_timestamp($stamp1, "file");
  486. return $content;
  487. } else {
  488. $stamp1 = microtime(true);
  489. $content = file_get_contents("view$engine/$s");
  490. $a->save_timestamp($stamp1, "file");
  491. return $content;
  492. }
  493. }}
  494. if(! function_exists('get_markup_template')) {
  495. /**
  496. * load template $s
  497. *
  498. * @param string $s
  499. * @param string $root
  500. * @return string
  501. */
  502. function get_markup_template($s, $root = '') {
  503. $stamp1 = microtime(true);
  504. $a = get_app();
  505. $t = $a->template_engine();
  506. try {
  507. $template = $t->get_template_file($s, $root);
  508. } catch (Exception $e) {
  509. echo "<pre><b>".__function__."</b>: ".$e->getMessage()."</pre>"; killme();
  510. }
  511. $a->save_timestamp($stamp1, "file");
  512. return $template;
  513. }}
  514. if(! function_exists("get_template_file")) {
  515. /**
  516. *
  517. * @param App $a
  518. * @param string $filename
  519. * @param string $root
  520. * @return string
  521. */
  522. function get_template_file($a, $filename, $root = '') {
  523. $theme = current_theme();
  524. // Make sure $root ends with a slash /
  525. if($root !== '' && $root[strlen($root)-1] !== '/')
  526. $root = $root . '/';
  527. if(file_exists("{$root}view/theme/$theme/$filename"))
  528. $template_file = "{$root}view/theme/$theme/$filename";
  529. elseif (x($a->theme_info,"extends") && file_exists("{$root}view/theme/{$a->theme_info["extends"]}/$filename"))
  530. $template_file = "{$root}view/theme/{$a->theme_info["extends"]}/$filename";
  531. elseif (file_exists("{$root}/$filename"))
  532. $template_file = "{$root}/$filename";
  533. else
  534. $template_file = "{$root}view/$filename";
  535. return $template_file;
  536. }}
  537. if(! function_exists('attribute_contains')) {
  538. /**
  539. * for html,xml parsing - let's say you've got
  540. * an attribute foobar="class1 class2 class3"
  541. * and you want to find out if it contains 'class3'.
  542. * you can't use a normal sub string search because you
  543. * might match 'notclass3' and a regex to do the job is
  544. * possible but a bit complicated.
  545. * pass the attribute string as $attr and the attribute you
  546. * are looking for as $s - returns true if found, otherwise false
  547. *
  548. * @param string $attr attribute value
  549. * @param string $s string to search
  550. * @return boolean True if found, False otherwise
  551. */
  552. function attribute_contains($attr,$s) {
  553. $a = explode(' ', $attr);
  554. if(count($a) && in_array($s,$a))
  555. return true;
  556. return false;
  557. }}
  558. if(! function_exists('logger')) {
  559. /* setup int->string log level map */
  560. $LOGGER_LEVELS = array();
  561. /**
  562. * log levels:
  563. * LOGGER_NORMAL (default)
  564. * LOGGER_TRACE
  565. * LOGGER_DEBUG
  566. * LOGGER_DATA
  567. * LOGGER_ALL
  568. *
  569. * @global App $a
  570. * @global dba $db
  571. * @param string $msg
  572. * @param int $level
  573. */
  574. function logger($msg,$level = 0) {
  575. // turn off logger in install mode
  576. global $a;
  577. global $db;
  578. global $LOGGER_LEVELS;
  579. if(($a->module == 'install') || (! ($db && $db->connected))) return;
  580. if (count($LOGGER_LEVELS)==0){
  581. foreach (get_defined_constants() as $k=>$v){
  582. if (substr($k,0,7)=="LOGGER_")
  583. $LOGGER_LEVELS[$v] = substr($k,7,7);
  584. }
  585. }
  586. $debugging = get_config('system','debugging');
  587. $loglevel = intval(get_config('system','loglevel'));
  588. $logfile = get_config('system','logfile');
  589. if((! $debugging) || (! $logfile) || ($level > $loglevel))
  590. return;
  591. $callers = debug_backtrace();
  592. $logline = sprintf("%s@%s\t[%s]:%s:%s:%s\t%s\n",
  593. datetime_convert(),
  594. session_id(),
  595. $LOGGER_LEVELS[$level],
  596. basename($callers[0]['file']),
  597. $callers[0]['line'],
  598. $callers[1]['function'],
  599. $msg
  600. );
  601. $stamp1 = microtime(true);
  602. @file_put_contents($logfile, $logline, FILE_APPEND);
  603. $a->save_timestamp($stamp1, "file");
  604. return;
  605. }}
  606. if(! function_exists('activity_match')) {
  607. /**
  608. * Compare activity uri. Knows about activity namespace.
  609. *
  610. * @param string $haystack
  611. * @param string $needle
  612. * @return boolean
  613. */
  614. function activity_match($haystack,$needle) {
  615. if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
  616. return true;
  617. return false;
  618. }}
  619. if(! function_exists('get_tags')) {
  620. /**
  621. * Pull out all #hashtags and @person tags from $s;
  622. * We also get @person@domain.com - which would make
  623. * the regex quite complicated as tags can also
  624. * end a sentence. So we'll run through our results
  625. * and strip the period from any tags which end with one.
  626. * Returns array of tags found, or empty array.
  627. *
  628. * @param string $s
  629. * @return array
  630. */
  631. function get_tags($s) {
  632. $ret = array();
  633. // Convert hashtag links to hashtags
  634. $s = preg_replace("/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism", "#$2", $s);
  635. // ignore anything in a code block
  636. $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
  637. // Force line feeds at bbtags
  638. $s = str_replace(array("[", "]"), array("\n[", "]\n"), $s);
  639. // ignore anything in a bbtag
  640. $s = preg_replace('/\[(.*?)\]/sm','',$s);
  641. // Match full names against @tags including the space between first and last
  642. // We will look these up afterward to see if they are full names or not recognisable.
  643. if(preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/',$s,$match)) {
  644. foreach($match[1] as $mtch) {
  645. if(strstr($mtch,"]")) {
  646. // we might be inside a bbcode color tag - leave it alone
  647. continue;
  648. }
  649. if(substr($mtch,-1,1) === '.')
  650. $ret[] = substr($mtch,0,-1);
  651. else
  652. $ret[] = $mtch;
  653. }
  654. }
  655. // Otherwise pull out single word tags. These can be @nickname, @first_last
  656. // and #hash tags.
  657. if(preg_match_all('/([!#@][^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/',$s,$match)) {
  658. foreach($match[1] as $mtch) {
  659. if(strstr($mtch,"]")) {
  660. // we might be inside a bbcode color tag - leave it alone
  661. continue;
  662. }
  663. if(substr($mtch,-1,1) === '.')
  664. $mtch = substr($mtch,0,-1);
  665. // ignore strictly numeric tags like #1
  666. if((strpos($mtch,'#') === 0) && ctype_digit(substr($mtch,1)))
  667. continue;
  668. // try not to catch url fragments
  669. if(strpos($s,$mtch) && preg_match('/[a-zA-z0-9\/]/',substr($s,strpos($s,$mtch)-1,1)))
  670. continue;
  671. $ret[] = $mtch;
  672. }
  673. }
  674. return $ret;
  675. }}
  676. //
  677. if(! function_exists('qp')) {
  678. /**
  679. * quick and dirty quoted_printable encoding
  680. *
  681. * @param string $s
  682. * @return string
  683. */
  684. function qp($s) {
  685. return str_replace ("%","=",rawurlencode($s));
  686. }}
  687. if(! function_exists('get_mentions')) {
  688. /**
  689. * @param array $item
  690. * @return string html for mentions #FIXME: remove html
  691. */
  692. function get_mentions($item) {
  693. $o = '';
  694. if(! strlen($item['tag']))
  695. return $o;
  696. $arr = explode(',',$item['tag']);
  697. foreach($arr as $x) {
  698. $matches = null;
  699. if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
  700. $o .= "\t\t" . '<link rel="ostatus:attention" href="' . $matches[1] . '" />' . "\r\n";
  701. $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
  702. }
  703. }
  704. if (!$item['private']) {
  705. $o .= "\t\t".'<link rel="ostatus:attention" href="http://activityschema.org/collection/public"/>'."\r\n";
  706. $o .= "\t\t".'<link rel="mentioned" href="http://activityschema.org/collection/public"/>'."\r\n";
  707. }
  708. return $o;
  709. }}
  710. if(! function_exists('contact_block')) {
  711. /**
  712. * Get html for contact block.
  713. *
  714. * @template contact_block.tpl
  715. * @hook contact_block_end (contacts=>array, output=>string)
  716. * @return string
  717. */
  718. function contact_block() {
  719. $o = '';
  720. $a = get_app();
  721. $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
  722. if($shown === false)
  723. $shown = 24;
  724. if($shown == 0)
  725. return;
  726. if((! is_array($a->profile)) || ($a->profile['hide-friends']))
  727. return $o;
  728. $r = q("SELECT COUNT(*) AS `total` FROM `contact`
  729. WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0
  730. AND `hidden` = 0 AND `archive` = 0
  731. AND `network` IN ('%s', '%s', '%s')",
  732. intval($a->profile['uid']),
  733. dbesc(NETWORK_DFRN),
  734. dbesc(NETWORK_OSTATUS),
  735. dbesc(NETWORK_DIASPORA)
  736. );
  737. if(count($r)) {
  738. $total = intval($r[0]['total']);
  739. }
  740. if(! $total) {
  741. $contacts = t('No contacts');
  742. $micropro = Null;
  743. } else {
  744. $r = q("SELECT * FROM `contact`
  745. WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0
  746. AND `hidden` = 0 AND `archive` = 0
  747. AND `network` IN ('%s', '%s', '%s') ORDER BY RAND() LIMIT %d",
  748. intval($a->profile['uid']),
  749. dbesc(NETWORK_DFRN),
  750. dbesc(NETWORK_OSTATUS),
  751. dbesc(NETWORK_DIASPORA),
  752. intval($shown)
  753. );
  754. if(count($r)) {
  755. $contacts = sprintf( tt('%d Contact','%d Contacts', $total),$total);
  756. $micropro = Array();
  757. foreach($r as $rr) {
  758. $micropro[] = micropro($rr,true,'mpfriend');
  759. }
  760. }
  761. }
  762. $tpl = get_markup_template('contact_block.tpl');
  763. $o = replace_macros($tpl, array(
  764. '$contacts' => $contacts,
  765. '$nickname' => $a->profile['nickname'],
  766. '$viewcontacts' => t('View Contacts'),
  767. '$micropro' => $micropro,
  768. ));
  769. $arr = array('contacts' => $r, 'output' => $o);
  770. call_hooks('contact_block_end', $arr);
  771. return $o;
  772. }}
  773. if(! function_exists('micropro')) {
  774. /**
  775. *
  776. * @param array $contact
  777. * @param boolean $redirect
  778. * @param string $class
  779. * @param boolean $textmode
  780. * @return string #FIXME: remove html
  781. */
  782. function micropro($contact, $redirect = false, $class = '', $textmode = false) {
  783. if($class)
  784. $class = ' ' . $class;
  785. $url = $contact['url'];
  786. $sparkle = '';
  787. $redir = false;
  788. if($redirect) {
  789. $a = get_app();
  790. $redirect_url = $a->get_baseurl() . '/redir/' . $contact['id'];
  791. if(local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === NETWORK_DFRN)) {
  792. $redir = true;
  793. $url = $redirect_url;
  794. $sparkle = ' sparkle';
  795. }
  796. else
  797. $url = zrl($url);
  798. }
  799. $click = ((x($contact,'click')) ? ' onclick="' . $contact['click'] . '" ' : '');
  800. if($click)
  801. $url = '';
  802. if($textmode) {
  803. return '<div class="contact-block-textdiv' . $class . '"><a class="contact-block-link' . $class . $sparkle
  804. . (($click) ? ' fakelink' : '') . '" '
  805. . (($redir) ? ' target="redir" ' : '')
  806. . (($url) ? ' href="' . $url . '"' : '') . $click
  807. . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name']
  808. . '" >'. $contact['name'] . '</a></div>' . "\r\n";
  809. }
  810. else {
  811. return '<div class="contact-block-div' . $class . '"><a class="contact-block-link' . $class . $sparkle
  812. . (($click) ? ' fakelink' : '') . '" '
  813. . (($redir) ? ' target="redir" ' : '')
  814. . (($url) ? ' href="' . $url . '"' : '') . $click . ' ><img class="contact-block-img' . $class . $sparkle . '" src="'
  815. . proxy_url($contact['micro']) . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name']
  816. . '" /></a></div>' . "\r\n";
  817. }
  818. }}
  819. if(! function_exists('search')) {
  820. /**
  821. * search box
  822. *
  823. * @param string $s search query
  824. * @param string $id html id
  825. * @param string $url search url
  826. * @param boolean $savedsearch show save search button
  827. */
  828. function search($s,$id='search-box',$url='/search',$save = false) {
  829. $a = get_app();
  830. return replace_macros(get_markup_template('searchbox.tpl'), array(
  831. '$s' => $s,
  832. '$id' => $id,
  833. '$action_url' => $a->get_baseurl((stristr($url,'network')) ? true : false) . $url,
  834. '$search_label' => t('Search'),
  835. '$save_label' => t('Save'),
  836. '$savedsearch' => feature_enabled(local_user(),'savedsearch'),
  837. ));
  838. }}
  839. if(! function_exists('valid_email')) {
  840. /**
  841. * Check if $x is a valid email string
  842. *
  843. * @param string $x
  844. * @return boolean
  845. */
  846. function valid_email($x){
  847. if(get_config('system','disable_email_validation'))
  848. return true;
  849. if(preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
  850. return true;
  851. return false;
  852. }}
  853. if(! function_exists('linkify')) {
  854. /**
  855. * Replace naked text hyperlink with HTML formatted hyperlink
  856. *
  857. * @param string $s
  858. */
  859. function linkify($s) {
  860. $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="_blank">$1</a>', $s);
  861. $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$s);
  862. return($s);
  863. }}
  864. /**
  865. * Load poke verbs
  866. *
  867. * @return array index is present tense verb
  868. value is array containing past tense verb, translation of present, translation of past
  869. * @hook poke_verbs pokes array
  870. */
  871. function get_poke_verbs() {
  872. // index is present tense verb
  873. // value is array containing past tense verb, translation of present, translation of past
  874. $arr = array(
  875. 'poke' => array( 'poked', t('poke'), t('poked')),
  876. 'ping' => array( 'pinged', t('ping'), t('pinged')),
  877. 'prod' => array( 'prodded', t('prod'), t('prodded')),
  878. 'slap' => array( 'slapped', t('slap'), t('slapped')),
  879. 'finger' => array( 'fingered', t('finger'), t('fingered')),
  880. 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')),
  881. );
  882. call_hooks('poke_verbs', $arr);
  883. return $arr;
  884. }
  885. /**
  886. * Load moods
  887. * @return array index is mood, value is translated mood
  888. * @hook mood_verbs moods array
  889. */
  890. function get_mood_verbs() {
  891. $arr = array(
  892. 'happy' => t('happy'),
  893. 'sad' => t('sad'),
  894. 'mellow' => t('mellow'),
  895. 'tired' => t('tired'),
  896. 'perky' => t('perky'),
  897. 'angry' => t('angry'),
  898. 'stupefied' => t('stupified'),
  899. 'puzzled' => t('puzzled'),
  900. 'interested' => t('interested'),
  901. 'bitter' => t('bitter'),
  902. 'cheerful' => t('cheerful'),
  903. 'alive' => t('alive'),
  904. 'annoyed' => t('annoyed'),
  905. 'anxious' => t('anxious'),
  906. 'cranky' => t('cranky'),
  907. 'disturbed' => t('disturbed'),
  908. 'frustrated' => t('frustrated'),
  909. 'motivated' => t('motivated'),
  910. 'relaxed' => t('relaxed'),
  911. 'surprised' => t('surprised'),
  912. );
  913. call_hooks('mood_verbs', $arr);
  914. return $arr;
  915. }
  916. if(! function_exists('smilies')) {
  917. /**
  918. * Replaces text emoticons with graphical images
  919. *
  920. * It is expected that this function will be called using HTML text.
  921. * We will escape text between HTML pre and code blocks from being
  922. * processed.
  923. *
  924. * At a higher level, the bbcode [nosmile] tag can be used to prevent this
  925. * function from being executed by the prepare_text() routine when preparing
  926. * bbcode source for HTML display
  927. *
  928. * @param string $s
  929. * @param boolean $sample
  930. * @return string
  931. * @hook smilie ('texts' => smilies texts array, 'icons' => smilies html array, 'string' => $s)
  932. */
  933. function smilies($s, $sample = false) {
  934. $a = get_app();
  935. if(intval(get_config('system','no_smilies'))
  936. || (local_user() && intval(get_pconfig(local_user(),'system','no_smilies'))))
  937. return $s;
  938. $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_encode',$s);
  939. $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_encode',$s);
  940. $texts = array(
  941. '&lt;3',
  942. '&lt;/3',
  943. '&lt;\\3',
  944. ':-)',
  945. ';-)',
  946. ':-(',
  947. ':-P',
  948. ':-p',
  949. ':-"',
  950. ':-&quot;',
  951. ':-x',
  952. ':-X',
  953. ':-D',
  954. '8-|',
  955. '8-O',
  956. ':-O',
  957. '\\o/',
  958. 'o.O',
  959. 'O.o',
  960. 'o_O',
  961. 'O_o',
  962. ":'(",
  963. ":-!",
  964. ":-/",
  965. ":-[",
  966. "8-)",
  967. ':beer',
  968. ':homebrew',
  969. ':coffee',
  970. ':facepalm',
  971. ':like',
  972. ':dislike',
  973. '~friendica',
  974. 'red#',
  975. 'red#matrix'
  976. );
  977. $icons = array(
  978. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="&lt;3" />',
  979. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="&lt;/3" />',
  980. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="&lt;\\3" />',
  981. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
  982. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
  983. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
  984. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
  985. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-p" />',
  986. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
  987. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
  988. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
  989. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
  990. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
  991. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
  992. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />',
  993. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt=":-O" />',
  994. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-thumbsup.gif" alt="\\o/" />',
  995. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-Oo.gif" alt="o.O" />',
  996. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-Oo.gif" alt="O.o" />',
  997. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-Oo.gif" alt="o_O" />',
  998. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-Oo.gif" alt="O_o" />',
  999. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-cry.gif" alt=":\'(" />',
  1000. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-foot-in-mouth.gif" alt=":-!" />',
  1001. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-undecided.gif" alt=":-/" />',
  1002. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-embarassed.gif" alt=":-[" />',
  1003. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-cool.gif" alt="8-)" />',
  1004. '<img class="smiley" src="' . $a->get_baseurl() . '/images/beer_mug.gif" alt=":beer" />',
  1005. '<img class="smiley" src="' . $a->get_baseurl() . '/images/beer_mug.gif" alt=":homebrew" />',
  1006. '<img class="smiley" src="' . $a->get_baseurl() . '/images/coffee.gif" alt=":coffee" />',
  1007. '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-facepalm.gif" alt=":facepalm" />',
  1008. '<img class="smiley" src="' . $a->get_baseurl() . '/images/like.gif" alt=":like" />',
  1009. '<img class="smiley" src="' . $a->get_baseurl() . '/images/dislike.gif" alt=":dislike" />',
  1010. '<a href="http://friendica.com">~friendica <img class="smiley" src="' . $a->get_baseurl() . '/images/friendica-16.png" alt="~friendica" /></a>',
  1011. '<a href="http://redmatrix.me/">red<img class="smiley" src="' . $a->get_baseurl() . '/images/rm-16.png" alt="red" />matrix</a>',
  1012. '<a href="http://redmatrix.me/">red<img class="smiley" src="' . $a->get_baseurl() . '/images/rm-16.png" alt="red" />matrix</a>'
  1013. );
  1014. $params = array('texts' => $texts, 'icons' => $icons, 'string' => $s);
  1015. call_hooks('smilie', $params);
  1016. if($sample) {
  1017. $s = '<div class="smiley-sample">';
  1018. for($x = 0; $x < count($params['texts']); $x ++) {
  1019. $s .= '<dl><dt>' . $params['texts'][$x] . '</dt><dd>' . $params['icons'][$x] . '</dd></dl>';
  1020. }
  1021. }
  1022. else {
  1023. $params['string'] = preg_replace_callback('/&lt;(3+)/','preg_heart',$params['string']);
  1024. $s = str_replace($params['texts'],$params['icons'],$params['string']);
  1025. }
  1026. $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_decode',$s);
  1027. $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_decode',$s);
  1028. return $s;
  1029. }}
  1030. function smile_encode($m) {
  1031. return(str_replace($m[1],base64url_encode($m[1]),$m[0]));
  1032. }
  1033. function smile_decode($m) {
  1034. return(str_replace($m[1],base64url_decode($m[1]),$m[0]));
  1035. }
  1036. /**
  1037. * expand <3333 to the correct number of hearts
  1038. *
  1039. * @param string $x
  1040. * @return string
  1041. */
  1042. function preg_heart($x) {
  1043. $a = get_app();
  1044. if(strlen($x[1]) == 1)
  1045. return $x[0];
  1046. $t = '';
  1047. for($cnt = 0; $cnt < strlen($x[1]); $cnt ++)
  1048. $t .= '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="&lt;3" />';
  1049. $r = str_replace($x[0],$t,$x[0]);
  1050. return $r;
  1051. }
  1052. if(! function_exists('day_translate')) {
  1053. /**
  1054. * Translate days and months names
  1055. *
  1056. * @param string $s
  1057. * @return string
  1058. */
  1059. function day_translate($s) {
  1060. $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
  1061. array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
  1062. $s);
  1063. $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
  1064. 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')),
  1065. $ret);
  1066. return $ret;
  1067. }}
  1068. if(! function_exists('normalise_link')) {
  1069. /**
  1070. * Normalize url
  1071. *
  1072. * @param string $url
  1073. * @return string
  1074. */
  1075. function normalise_link($url) {
  1076. $ret = str_replace(array('https:','//www.'), array('http:','//'), $url);
  1077. return(rtrim($ret,'/'));
  1078. }}
  1079. if(! function_exists('link_compare')) {
  1080. /**
  1081. * Compare two URLs to see if they are the same, but ignore
  1082. * slight but hopefully insignificant differences such as if one
  1083. * is https and the other isn't, or if one is www.something and
  1084. * the other isn't - and also ignore case differences.
  1085. *
  1086. * @param string $a first url
  1087. * @param string $b second url
  1088. * @return boolean True if the URLs match, otherwise False
  1089. *
  1090. */
  1091. function link_compare($a,$b) {
  1092. if(strcasecmp(normalise_link($a),normalise_link($b)) === 0)
  1093. return true;
  1094. return false;
  1095. }}
  1096. if(! function_exists('redir_private_images')) {
  1097. /**
  1098. * Find any non-embedded images in private items and add redir links to them
  1099. *
  1100. * @param App $a
  1101. * @param array $item
  1102. */
  1103. function redir_private_images($a, &$item) {
  1104. $matches = false;
  1105. $cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
  1106. if($cnt) {
  1107. //logger("redir_private_images: matches = " . print_r($matches, true));
  1108. foreach($matches as $mtch) {
  1109. if(strpos($mtch[1], '/redir') !== false)
  1110. continue;
  1111. if((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
  1112. //logger("redir_private_images: redir");
  1113. $img_url = $a->get_baseurl() . '/redir?f=1&quiet=1&url=' . $mtch[1] . '&conurl=' . $item['author-link'];
  1114. $item['body'] = str_replace($mtch[0], "[img]".$img_url."[/img]", $item['body']);
  1115. }
  1116. }
  1117. }
  1118. }}
  1119. function put_item_in_cache(&$item, $update = false) {
  1120. if (($item["rendered-hash"] != hash("md5", $item["body"])) OR ($item["rendered-hash"] == "") OR
  1121. ($item["rendered-html"] == "") OR get_config("system", "ignore_cache")) {
  1122. // The function "redir_private_images" changes the body.
  1123. // I'm not sure if we should store it permanently, so we save the old value.
  1124. $body = $item["body"];
  1125. $a = get_app();
  1126. redir_private_images($a, $item);
  1127. $item["rendered-html"] = prepare_text($item["body"]);
  1128. $item["rendered-hash"] = hash("md5", $item["body"]);
  1129. $item["body"] = $body;
  1130. if ($update AND ($item["id"] != 0)) {
  1131. q("UPDATE `item` SET `rendered-html` = '%s', `rendered-hash` = '%s' WHERE `id` = %d",
  1132. dbesc($item["rendered-html"]), dbesc($item["rendered-hash"]), intval($item["id"]));
  1133. }
  1134. }
  1135. }
  1136. // Given an item array, convert the body element from bbcode to html and add smilie icons.
  1137. // If attach is true, also add icons for item attachments
  1138. if(! function_exists('prepare_body')) {
  1139. /**
  1140. * Given an item array, convert the body element from bbcode to html and add smilie icons.
  1141. * If attach is true, also add icons for item attachments
  1142. *
  1143. * @param array $item
  1144. * @param boolean $attach
  1145. * @return string item body html
  1146. * @hook prepare_body_init item array before any work
  1147. * @hook prepare_body ('item'=>item array, 'html'=>body string) after first bbcode to html
  1148. * @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
  1149. */
  1150. function prepare_body(&$item,$attach = false, $preview = false) {
  1151. $a = get_app();
  1152. call_hooks('prepare_body_init', $item);
  1153. $searchpath = $a->get_baseurl()."/search?tag=";
  1154. $tags=array();
  1155. $hashtags = array();
  1156. $mentions = array();
  1157. if (!get_config('system','suppress_tags')) {
  1158. $taglist = q("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` IN (%d, %d) ORDER BY `tid`",
  1159. intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
  1160. foreach($taglist as $tag) {
  1161. if ($tag["url"] == "")
  1162. $tag["url"] = $searchpath.strtolower($tag["term"]);
  1163. if ($tag["type"] == TERM_HASHTAG) {
  1164. $hashtags[] = "#<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
  1165. $prefix = "#";
  1166. } elseif ($tag["type"] == TERM_MENTION) {
  1167. $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
  1168. $prefix = "@";
  1169. }
  1170. $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
  1171. }
  1172. }
  1173. $item['tags'] = $tags;
  1174. $item['hashtags'] = $hashtags;
  1175. $item['mentions'] = $mentions;
  1176. put_item_in_cache($item, true);
  1177. $s = $item["rendered-html"];
  1178. require_once("mod/proxy.php");
  1179. $s = proxy_parse_html($s);
  1180. $prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview);
  1181. call_hooks('prepare_body', $prep_arr);
  1182. $s = $prep_arr['html'];
  1183. if(! $attach) {
  1184. // Replace the blockquotes with quotes that are used in mails
  1185. $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
  1186. $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
  1187. return $s;
  1188. }
  1189. $as = '';
  1190. $vhead = false;
  1191. $arr = explode('[/attach],',$item['attach']);
  1192. if(count($arr)) {
  1193. $as .= '<div class="body-attach">';
  1194. foreach($arr as $r) {
  1195. $matches = false;
  1196. $icon = '';
  1197. $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches, PREG_SET_ORDER);
  1198. if($cnt) {
  1199. foreach($matches as $mtch) {
  1200. $mime = $mtch[3];
  1201. if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN))
  1202. $the_url = $a->get_baseurl() . '/redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
  1203. else
  1204. $the_url = $mtch[1];
  1205. if(strpos($mime, 'video') !== false) {
  1206. if(!$vhead) {
  1207. $vhead = true;
  1208. $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
  1209. '$baseurl' => $a->get_baseurl(),
  1210. ));
  1211. $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
  1212. '$baseurl' => $a->get_baseurl(),
  1213. ));
  1214. }
  1215. $id = end(explode('/', $the_url));
  1216. $as .= replace_macros(get_markup_template('video_top.tpl'), array(
  1217. '$video' => array(
  1218. 'id' => $id,
  1219. 'title' => t('View Video'),
  1220. 'src' => $the_url,
  1221. 'mime' => $mime,
  1222. ),
  1223. ));
  1224. }
  1225. $filetype = strtolower(substr( $mime, 0, strpos($mime,'/') ));
  1226. if($filetype) {
  1227. $filesubtype = strtolower(substr( $mime, strpos($mime,'/') + 1 ));
  1228. $filesubtype = str_replace('.', '-', $filesubtype);
  1229. }
  1230. else {
  1231. $filetype = 'unkn';
  1232. $filesubtype = 'unkn';
  1233. }
  1234. $icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>';
  1235. /*$icontype = strtolower(substr($mtch[3],0,strpos($mtch[3],'/')));
  1236. switch($icontype) {
  1237. case 'video':
  1238. case 'audio':
  1239. case 'image':
  1240. case 'text':
  1241. $icon = '<div class="attachtype icon s22 type-' . $icontype . '"></div>';
  1242. break;
  1243. default:
  1244. $icon = '<div class="attachtype icon s22 type-unkn"></div>';
  1245. break;
  1246. }*/
  1247. $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
  1248. $title .= ' ' . $mtch[2] . ' ' . t('bytes');
  1249. $as .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" target="_blank" >' . $icon . '</a>';
  1250. }
  1251. }
  1252. }
  1253. $as .= '<div class="clear"></div></div>';
  1254. }
  1255. $s = $s . $as;
  1256. // map
  1257. if(strpos($s,'<div class="map">') !== false && $item['coord']) {
  1258. $x = generate_map(trim($item['coord']));
  1259. if($x) {
  1260. $s = preg_replace('/\<div class\=\"map\"\>/','$0' . $x,$s);
  1261. }
  1262. }
  1263. // Look for spoiler
  1264. $spoilersearch = '<blockquote class="spoiler">';
  1265. // Remove line breaks before the spoiler
  1266. while ((strpos($s, "\n".$spoilersearch) !== false))
  1267. $s = str_replace("\n".$spoilersearch, $spoilersearch, $s);
  1268. while ((strpos($s, "<br />".$spoilersearch) !== false))
  1269. $s = str_replace("<br />".$spoilersearch, $spoilersearch, $s);
  1270. while ((strpos($s, $spoilersearch) !== false)) {
  1271. $pos = strpos($s, $spoilersearch);
  1272. $rnd = random_string(8);
  1273. $spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
  1274. '<blockquote class="spoiler" id="spoiler-'.$rnd.'" style="display: none;">';
  1275. $s = substr($s, 0, $pos).$spoilerreplace.substr($s, $pos+strlen($spoilersearch));
  1276. }
  1277. // Look for quote with author
  1278. $authorsearch = '<blockquote class="author">';
  1279. while ((strpos($s, $authorsearch) !== false)) {
  1280. $pos = strpos($s, $authorsearch);
  1281. $rnd = random_string(8);
  1282. $authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
  1283. '<blockquote class="author" id="author-'.$rnd.'" style="display: block;">';
  1284. $s = substr($s, 0, $pos).$authorreplace.substr($s, $pos+strlen($authorsearch));
  1285. }
  1286. // replace friendica image url size with theme preference
  1287. if (x($a->theme_info,'item_image_size')){
  1288. $ps = $a->theme_info['item_image_size'];
  1289. $s = preg_replace('|(<img[^>]+src="[^"]+/photo/[0-9a-f]+)-[0-9]|',"$1-".$ps, $s);
  1290. }
  1291. $prep_arr = array('item' => $item, 'html' => $s);
  1292. call_hooks('prepare_body_final', $prep_arr);
  1293. return $prep_arr['html'];
  1294. }}
  1295. if(! function_exists('prepare_text')) {
  1296. /**
  1297. * Given a text string, convert from bbcode to html and add smilie icons.
  1298. *
  1299. * @param string $text
  1300. * @return string
  1301. */
  1302. function prepare_text($text) {
  1303. require_once('include/bbcode.php');
  1304. if(stristr($text,'[nosmile]'))
  1305. $s = bbcode($text);
  1306. else
  1307. $s = smilies(bbcode($text));
  1308. return trim($s);
  1309. }}
  1310. /**
  1311. * return array with details for categories and folders for an item
  1312. *
  1313. * @param array $item
  1314. * @return array
  1315. *
  1316. * [
  1317. * [ // categories array
  1318. * {
  1319. * 'name': 'category name',
  1320. * 'removeurl': 'url to remove this category',
  1321. * 'first': 'is the first in this array? true/false',
  1322. * 'last': 'is the last in this array? true/false',
  1323. * } ,
  1324. * ....
  1325. * ],
  1326. * [ //folders array
  1327. * {
  1328. * 'name': 'folder name',
  1329. * 'removeurl': 'url to remove this folder',
  1330. * 'first': 'is the first in this array? true/false',
  1331. * 'last': 'is the last in this array? true/false',
  1332. * } ,
  1333. * ....
  1334. * ]
  1335. * ]
  1336. */
  1337. function get_cats_and_terms($item) {
  1338. $a = get_app();
  1339. $categories = array();
  1340. $folders = array();
  1341. $matches = false; $first = true;
  1342. $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
  1343. if($cnt) {
  1344. foreach($matches as $mtch) {
  1345. $categories[] = array(
  1346. 'name' => xmlify(file_tag_decode($mtch[1])),
  1347. 'url' => "#",
  1348. 'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""),
  1349. 'first' => $first,
  1350. 'last' => false
  1351. );
  1352. $first = false;
  1353. }
  1354. }
  1355. if (count($categories)) $categories[count($categories)-1]['last'] = true;
  1356. if(local_user() == $item['uid']) {
  1357. $matches = false; $first = true;
  1358. $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
  1359. if($cnt) {
  1360. foreach($matches as $mtch) {
  1361. $folders[] = array(
  1362. 'name' => xmlify(file_tag_decode($mtch[1])),
  1363. 'url' => "#",
  1364. 'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""),
  1365. 'first' => $first,
  1366. 'last' => false
  1367. );
  1368. $first = false;
  1369. }
  1370. }
  1371. }
  1372. if (count($folders)) $folders[count($folders)-1]['last'] = true;
  1373. return array($categories, $folders);
  1374. }
  1375. if(! function_exists('feed_hublinks')) {
  1376. /**
  1377. * return atom link elements for all of our hubs
  1378. * @return string hub link xml elements
  1379. */
  1380. function feed_hublinks() {
  1381. $a = get_app();
  1382. $hub = get_config('system','huburl');
  1383. $hubxml = '';
  1384. if(strlen($hub)) {
  1385. $hubs = explode(',', $hub);
  1386. if(count($hubs)) {
  1387. foreach($hubs as $h) {
  1388. $h = trim($h);
  1389. if(! strlen($h))
  1390. continue;
  1391. if ($h === '[internal]')
  1392. $h = $a->get_baseurl() . '/pubsubhubbub';
  1393. $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
  1394. }
  1395. }
  1396. }
  1397. return $hubxml;
  1398. }}
  1399. if(! function_exists('feed_salmonlinks')) {
  1400. /**
  1401. * return atom link elements for salmon endpoints
  1402. * @param string $nick user nickname
  1403. * @return string salmon link xml elements
  1404. */
  1405. function feed_salmonlinks($nick) {
  1406. $a = get_app();
  1407. $salmon = '<link rel="salmon" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
  1408. // old style links that status.net still needed as of 12/2010
  1409. $salmon .= ' <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
  1410. $salmon .= ' <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
  1411. return $salmon;
  1412. }}
  1413. if(! function_exists('get_plink')) {
  1414. /**
  1415. * get private link for item
  1416. * @param array $item
  1417. * @return boolean|array False if item has not plink, otherwise array('href'=>plink url, 'title'=>translated title)
  1418. */
  1419. function get_plink($item) {
  1420. $a = get_app();
  1421. if ($a->user['nickname'] != "") {
  1422. $ret = array(
  1423. //'href' => $a->get_baseurl()."/display/".$a->user['nickname']."/".$item['id'],
  1424. 'href' => $a->get_baseurl()."/display/".$item['guid'],
  1425. 'orig' => $a->get_baseurl()."/display/".$item['guid'],
  1426. 'title' => t('local thread'),
  1427. 'orig_title' => t('local thread'),
  1428. );
  1429. if (x($item,'plink')) {
  1430. $ret["href"] = $item['plink'];
  1431. $ret["title"] = t('link to source');
  1432. }
  1433. } elseif (x($item,'plink') && ($item['private'] != 1))
  1434. $ret = array(
  1435. 'href' => $item['plink'],
  1436. 'orig' => $item['plink'],
  1437. 'title' => t('link to source'),
  1438. );
  1439. else
  1440. $ret = array();
  1441. //if (x($item,'plink') && ($item['private'] != 1))
  1442. return($ret);
  1443. }}
  1444. if(! function_exists('unamp')) {
  1445. /**
  1446. * replace html amp entity with amp char
  1447. * @param string $s
  1448. * @return string
  1449. */
  1450. function unamp($s) {
  1451. return str_replace('&amp;', '&', $s);
  1452. }}
  1453. if(! function_exists('lang_selector')) {
  1454. /**
  1455. * get html for language selector
  1456. * @global string $lang
  1457. * @return string
  1458. * @template lang_selector.tpl
  1459. */
  1460. function lang_selector() {
  1461. global $lang;
  1462. $langs = glob('view/*/strings.php');
  1463. $lang_options = array();
  1464. $selected = "";
  1465. if(is_array($langs) && count($langs)) {
  1466. $langs[] = '';
  1467. if(! in_array('view/en/strings.php',$langs))
  1468. $langs[] = 'view/en/';
  1469. asort($langs);
  1470. foreach($langs as $l) {
  1471. if($l == '') {
  1472. $lang_options[""] = t('default');
  1473. continue;
  1474. }
  1475. $ll = substr($l,5);
  1476. $ll = substr($ll,0,strrpos($ll,'/'));
  1477. $selected = (($ll === $lang && (x($_SESSION, 'language'))) ? $ll : $selected);
  1478. $lang_options[$ll]=$ll;
  1479. }
  1480. }
  1481. $tpl = get_markup_template("lang_selector.tpl");
  1482. $o = replace_macros($tpl, array(
  1483. '$title' => t('Select an alternate language'),
  1484. '$langs' => array($lang_options, $selected),
  1485. ));
  1486. return $o;
  1487. }}
  1488. if(! function_exists('return_bytes')) {
  1489. /**
  1490. * return number of bytes in size (K, M, G)
  1491. * @param string $size_str
  1492. * @return number
  1493. */
  1494. function return_bytes ($size_str) {
  1495. switch (substr ($size_str, -1))
  1496. {
  1497. case 'M': case 'm': return (int)$size_str * 1048576;
  1498. case 'K': case 'k': return (int)$size_str * 1024;
  1499. case 'G': case 'g': return (int)$size_str * 1073741824;
  1500. default: return $size_str;
  1501. }
  1502. }}
  1503. /**
  1504. * @return string
  1505. */
  1506. function generate_user_guid() {
  1507. $found = true;
  1508. do {
  1509. $guid = get_guid(32);
  1510. $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
  1511. dbesc($guid)
  1512. );
  1513. if(! count($x))
  1514. $found = false;
  1515. } while ($found == true );
  1516. return $guid;
  1517. }
  1518. /**
  1519. * @param string $s
  1520. * @param boolean $strip_padding
  1521. * @return string
  1522. */
  1523. function base64url_encode($s, $strip_padding = false) {
  1524. $s = strtr(base64_encode($s),'+/','-_');
  1525. if($strip_padding)
  1526. $s = str_replace('=','',$s);
  1527. return $s;
  1528. }
  1529. /**
  1530. * @param string $s
  1531. * @return string
  1532. */
  1533. function base64url_decode($s) {
  1534. if(is_array($s)) {
  1535. logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
  1536. return $s;
  1537. }
  1538. /*
  1539. * // Placeholder for new rev of salmon which strips base64 padding.
  1540. * // PHP base64_decode handles the un-padded input without requiring this step
  1541. * // Uncomment if you find you need it.
  1542. *
  1543. * $l = strlen($s);
  1544. * if(! strpos($s,'=')) {
  1545. * $m = $l % 4;
  1546. * if($m == 2)
  1547. * $s .= '==';
  1548. * if($m == 3)
  1549. * $s .= '=';
  1550. * }
  1551. *
  1552. */
  1553. return base64_decode(strtr($s,'-_','+/'));
  1554. }
  1555. if (!function_exists('str_getcsv')) {
  1556. /**
  1557. * Parse csv string
  1558. *
  1559. * @param string $input
  1560. * @param string $delimiter
  1561. * @param string $enclosure
  1562. * @param string $escape
  1563. * @param string $eol
  1564. * @return boolean|array False on error, otherwise array[row][column]
  1565. */
  1566. function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
  1567. if (is_string($input) && !empty($input)) {
  1568. $output = array();
  1569. $tmp = preg_split("/".$eol."/",$input);
  1570. if (is_array($tmp) && !empty($tmp)) {
  1571. while (list($line_num, $line) = each($tmp)) {
  1572. if (preg_match("/".$escape.$enclosure."/",$line)) {
  1573. while ($strlen = strlen($line)) {
  1574. $pos_delimiter = strpos($line,$delimiter);
  1575. $pos_enclosure_start = strpos($line,$enclosure);
  1576. if (
  1577. is_int($pos_delimiter) && is_int($pos_enclosure_start)
  1578. && ($pos_enclosure_start < $pos_delimiter)
  1579. ) {
  1580. $enclosed_str = substr($line,1);
  1581. $pos_enclosure_end = strpos($enclosed_str,$enclosure);
  1582. $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
  1583. $output[$line_num][] = $enclosed_str;
  1584. $offset = $pos_enclosure_end+3;
  1585. } else {
  1586. if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
  1587. $output[$line_num][] = substr($line,0);
  1588. $offset = strlen($line);
  1589. } else {
  1590. $output[$line_num][] = substr($line,0,$pos_delimiter);
  1591. $offset = (
  1592. !empty($pos_enclosure_start)
  1593. && ($pos_enclosure_start < $pos_delimiter)
  1594. )
  1595. ?$pos_enclosure_start
  1596. :$pos_delimiter+1;
  1597. }
  1598. }
  1599. $line = substr($line,$offset);
  1600. }
  1601. } else {
  1602. $line = preg_split("/".$delimiter."/",$line);
  1603. /*
  1604. * Validating against pesky extra line breaks creating false rows.
  1605. */
  1606. if (is_array($line) && !empty($line[0])) {
  1607. $output[$line_num] = $line;
  1608. }
  1609. }
  1610. }
  1611. return $output;
  1612. } else {
  1613. return false;
  1614. }
  1615. } else {
  1616. return false;
  1617. }
  1618. }
  1619. }
  1620. /**
  1621. * return div element with class 'clear'
  1622. * @return string
  1623. * @deprecated
  1624. */
  1625. function cleardiv() {
  1626. return '<div class="clear"></div>';
  1627. }
  1628. function bb_translate_video($s) {
  1629. $matches = null;
  1630. $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
  1631. if($r) {
  1632. foreach($matches as $mtch) {
  1633. if((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
  1634. $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
  1635. elseif(stristr($mtch[1],'vimeo'))
  1636. $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
  1637. }
  1638. }
  1639. return $s;
  1640. }
  1641. function html2bb_video($s) {
  1642. $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
  1643. '[youtube]$2[/youtube]', $s);
  1644. $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
  1645. '[youtube]$2[/youtube]', $s);
  1646. $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
  1647. '[vimeo]$2[/vimeo]', $s);
  1648. return $s;
  1649. }
  1650. /**
  1651. * apply xmlify() to all values of array $val, recursively
  1652. * @param array $val
  1653. * @return array
  1654. */
  1655. function array_xmlify($val){
  1656. if (is_bool($val)) return $val?"true":"false";
  1657. if (is_array($val)) return array_map('array_xmlify', $val);
  1658. return xmlify((string) $val);
  1659. }
  1660. /**
  1661. * transorm link href and img src from relative to absolute
  1662. *
  1663. * @param string $text
  1664. * @param string $base base url
  1665. * @return string
  1666. */
  1667. function reltoabs($text, $base)
  1668. {
  1669. if (empty($base))
  1670. return $text;
  1671. $base = rtrim($base,'/');
  1672. $base2 = $base . "/";
  1673. // Replace links
  1674. $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
  1675. $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
  1676. $text = preg_replace($pattern, $replace, $text);
  1677. $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
  1678. $replace = "<a\${1} href=\"" . $base . "\${2}\"";
  1679. $text = preg_replace($pattern, $replace, $text);
  1680. // Replace images
  1681. $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
  1682. $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
  1683. $text = preg_replace($