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.

2246 lines
58KB

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