Hubzilla core code
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.
 
 
 
 
 
 

2895 lines
80 KiB

  1. <?php
  2. /**
  3. * @file include/text.php
  4. */
  5. require_once("include/bbcode.php");
  6. // random string, there are 86 characters max in text mode, 128 for hex
  7. // output is urlsafe
  8. define('RANDOM_STRING_HEX', 0x00 );
  9. define('RANDOM_STRING_TEXT', 0x01 );
  10. /**
  11. * @brief This is our template processor.
  12. *
  13. * @param string|SmartyEngine $s the string requiring macro substitution,
  14. * or an instance of SmartyEngine
  15. * @param array $r key value pairs (search => replace)
  16. * @return string substituted string
  17. */
  18. function replace_macros($s, $r) {
  19. $a = get_app();
  20. $arr = array('template' => $s, 'params' => $r);
  21. call_hooks('replace_macros', $arr);
  22. $t = App::template_engine();
  23. $output = $t->replace_macros($arr['template'],$arr['params']);
  24. return $output;
  25. }
  26. /**
  27. * @brief Generates a random string.
  28. *
  29. * @param number $size
  30. * @param int $type
  31. * @return string
  32. */
  33. function random_string($size = 64, $type = RANDOM_STRING_HEX) {
  34. // generate a bit of entropy and run it through the whirlpool
  35. $s = hash('whirlpool', (string) rand() . uniqid(rand(),true) . (string) rand(),(($type == RANDOM_STRING_TEXT) ? true : false));
  36. $s = (($type == RANDOM_STRING_TEXT) ? str_replace("\n","",base64url_encode($s,true)) : $s);
  37. return(substr($s, 0, $size));
  38. }
  39. /**
  40. * @brief 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. /**
  62. * use this on "body" or "content" input where angle chars shouldn't be removed,
  63. * and allow them to be safely displayed.
  64. * @param string $string
  65. * @return string
  66. */
  67. function escape_tags($string) {
  68. return(htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false));
  69. }
  70. function z_input_filter($channel_id,$s,$type = 'text/bbcode') {
  71. if($type === 'text/bbcode')
  72. return escape_tags($s);
  73. if($type === 'text/markdown')
  74. return escape_tags($s);
  75. if($type == 'text/plain')
  76. return escape_tags($s);
  77. if($type == 'application/x-pdl')
  78. return escape_tags($s);
  79. $a = get_app();
  80. if(App::$is_sys) {
  81. return $s;
  82. }
  83. $r = q("select account_id, account_roles, channel_pageflags from account left join channel on channel_account_id = account_id where channel_id = %d limit 1",
  84. intval($channel_id)
  85. );
  86. if($r) {
  87. if(($r[0]['account_roles'] & ACCOUNT_ROLE_ALLOWCODE) || ($r[0]['channel_pageflags'] & PAGE_ALLOWCODE)) {
  88. if(local_channel() && (get_account_id() == $r[0]['account_id'])) {
  89. return $s;
  90. }
  91. }
  92. }
  93. if($type === 'text/html')
  94. return purify_html($s);
  95. return escape_tags($s);
  96. }
  97. function purify_html($s, $allow_position = false) {
  98. require_once('library/HTMLPurifier.auto.php');
  99. require_once('include/html2bbcode.php');
  100. /**
  101. * @FIXME this function has html output, not bbcode - so safely purify these
  102. * $s = html2bb_video($s);
  103. * $s = oembed_html2bbcode($s);
  104. */
  105. $config = HTMLPurifier_Config::createDefault();
  106. $config->set('Cache.DefinitionImpl', null);
  107. $config->set('Attr.EnableID', true);
  108. //Allow some custom data- attributes used by built-in libs.
  109. //In this way members which do not have allowcode set can still use the built-in js libs in webpages to some extent.
  110. $def = $config->getHTMLDefinition(true);
  111. //data- attributes used by the foundation library
  112. $def->info_global_attr['data-options'] = new HTMLPurifier_AttrDef_Text;
  113. $def->info_global_attr['data-magellan-expedition'] = new HTMLPurifier_AttrDef_Text;
  114. $def->info_global_attr['data-magellan-destination'] = new HTMLPurifier_AttrDef_Text;
  115. $def->info_global_attr['data-magellan-arrival'] = new HTMLPurifier_AttrDef_Text;
  116. $def->info_global_attr['data-offcanvas'] = new HTMLPurifier_AttrDef_Text;
  117. $def->info_global_attr['data-topbar'] = new HTMLPurifier_AttrDef_Text;
  118. $def->info_global_attr['data-orbit'] = new HTMLPurifier_AttrDef_Text;
  119. $def->info_global_attr['data-orbit-slide-number'] = new HTMLPurifier_AttrDef_Text;
  120. $def->info_global_attr['data-dropdown'] = new HTMLPurifier_AttrDef_Text;
  121. $def->info_global_attr['data-dropdown-content'] = new HTMLPurifier_AttrDef_Text;
  122. $def->info_global_attr['data-reveal-id'] = new HTMLPurifier_AttrDef_Text;
  123. $def->info_global_attr['data-reveal'] = new HTMLPurifier_AttrDef_Text;
  124. $def->info_global_attr['data-alert'] = new HTMLPurifier_AttrDef_Text;
  125. $def->info_global_attr['data-tooltip'] = new HTMLPurifier_AttrDef_Text;
  126. $def->info_global_attr['data-joyride'] = new HTMLPurifier_AttrDef_Text;
  127. $def->info_global_attr['data-id'] = new HTMLPurifier_AttrDef_Text;
  128. $def->info_global_attr['data-text'] = new HTMLPurifier_AttrDef_Text;
  129. $def->info_global_attr['data-class'] = new HTMLPurifier_AttrDef_Text;
  130. $def->info_global_attr['data-prev-tex'] = new HTMLPurifier_AttrDef_Text;
  131. $def->info_global_attr['data-button'] = new HTMLPurifier_AttrDef_Text;
  132. $def->info_global_attr['data-accordion'] = new HTMLPurifier_AttrDef_Text;
  133. $def->info_global_attr['data-tab'] = new HTMLPurifier_AttrDef_Text;
  134. $def->info_global_attr['data-equalizer'] = new HTMLPurifier_AttrDef_Text;
  135. $def->info_global_attr['data-equalizer-watch'] = new HTMLPurifier_AttrDef_Text;
  136. //data- attributes used by the bootstrap library
  137. $def->info_global_attr['data-dismiss'] = new HTMLPurifier_AttrDef_Text;
  138. $def->info_global_attr['data-target'] = new HTMLPurifier_AttrDef_Text;
  139. $def->info_global_attr['data-toggle'] = new HTMLPurifier_AttrDef_Text;
  140. $def->info_global_attr['data-backdrop'] = new HTMLPurifier_AttrDef_Text;
  141. $def->info_global_attr['data-keyboard'] = new HTMLPurifier_AttrDef_Text;
  142. $def->info_global_attr['data-show'] = new HTMLPurifier_AttrDef_Text;
  143. $def->info_global_attr['data-spy'] = new HTMLPurifier_AttrDef_Text;
  144. $def->info_global_attr['data-offset'] = new HTMLPurifier_AttrDef_Text;
  145. $def->info_global_attr['data-animation'] = new HTMLPurifier_AttrDef_Text;
  146. $def->info_global_attr['data-container'] = new HTMLPurifier_AttrDef_Text;
  147. $def->info_global_attr['data-delay'] = new HTMLPurifier_AttrDef_Text;
  148. $def->info_global_attr['data-placement'] = new HTMLPurifier_AttrDef_Text;
  149. $def->info_global_attr['data-title'] = new HTMLPurifier_AttrDef_Text;
  150. $def->info_global_attr['data-trigger'] = new HTMLPurifier_AttrDef_Text;
  151. $def->info_global_attr['data-content'] = new HTMLPurifier_AttrDef_Text;
  152. $def->info_global_attr['data-trigger'] = new HTMLPurifier_AttrDef_Text;
  153. $def->info_global_attr['data-parent'] = new HTMLPurifier_AttrDef_Text;
  154. $def->info_global_attr['data-ride'] = new HTMLPurifier_AttrDef_Text;
  155. $def->info_global_attr['data-slide-to'] = new HTMLPurifier_AttrDef_Text;
  156. $def->info_global_attr['data-slide'] = new HTMLPurifier_AttrDef_Text;
  157. $def->info_global_attr['data-interval'] = new HTMLPurifier_AttrDef_Text;
  158. $def->info_global_attr['data-pause'] = new HTMLPurifier_AttrDef_Text;
  159. $def->info_global_attr['data-wrap'] = new HTMLPurifier_AttrDef_Text;
  160. $def->info_global_attr['data-offset-top'] = new HTMLPurifier_AttrDef_Text;
  161. $def->info_global_attr['data-offset-bottom'] = new HTMLPurifier_AttrDef_Text;
  162. //some html5 elements
  163. $def->addElement('section', 'Block', 'Flow', 'Common');
  164. $def->addElement('nav', 'Block', 'Flow', 'Common');
  165. $def->addElement('article', 'Block', 'Flow', 'Common');
  166. $def->addElement('aside', 'Block', 'Flow', 'Common');
  167. $def->addElement('header', 'Block', 'Flow', 'Common');
  168. $def->addElement('footer', 'Block', 'Flow', 'Common');
  169. if($allow_position) {
  170. $cssDefinition = $config->getCSSDefinition();
  171. $cssDefinition->info['position'] = new HTMLPurifier_AttrDef_Enum(array('absolute', 'fixed', 'relative', 'static', 'inherit'), false);
  172. $cssDefinition->info['left'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  173. new HTMLPurifier_AttrDef_CSS_Length(),
  174. new HTMLPurifier_AttrDef_CSS_Percentage()
  175. ));
  176. $cssDefinition->info['right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  177. new HTMLPurifier_AttrDef_CSS_Length(),
  178. new HTMLPurifier_AttrDef_CSS_Percentage()
  179. ));
  180. $cssDefinition->info['top'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  181. new HTMLPurifier_AttrDef_CSS_Length(),
  182. new HTMLPurifier_AttrDef_CSS_Percentage()
  183. ));
  184. $cssDefinition->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  185. new HTMLPurifier_AttrDef_CSS_Length(),
  186. new HTMLPurifier_AttrDef_CSS_Percentage()
  187. ));
  188. }
  189. $purifier = new HTMLPurifier($config);
  190. return $purifier->purify($s);
  191. }
  192. /**
  193. * @brief generate a string that's random, but usually pronounceable.
  194. *
  195. * Used to generate initial passwords.
  196. *
  197. * @param int $len
  198. * @return string
  199. */
  200. function autoname($len) {
  201. if ($len <= 0)
  202. return '';
  203. $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u');
  204. if (mt_rand(0, 5) == 4)
  205. $vowels[] = 'y';
  206. $cons = array(
  207. 'b','bl','br',
  208. 'c','ch','cl','cr',
  209. 'd','dr',
  210. 'f','fl','fr',
  211. 'g','gh','gl','gr',
  212. 'h',
  213. 'j',
  214. 'k','kh','kl','kr',
  215. 'l',
  216. 'm',
  217. 'n',
  218. 'p','ph','pl','pr',
  219. 'qu',
  220. 'r','rh',
  221. 's','sc','sh','sm','sp','st',
  222. 't','th','tr',
  223. 'v',
  224. 'w','wh',
  225. 'x',
  226. 'z','zh'
  227. );
  228. $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
  229. 'nd','ng','nk','nt','rn','rp','rt');
  230. $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
  231. 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
  232. $start = mt_rand(0, 2);
  233. if ($start == 0)
  234. $table = $vowels;
  235. else
  236. $table = $cons;
  237. $word = '';
  238. for ($x = 0; $x < $len; $x ++) {
  239. $r = mt_rand(0,count($table) - 1);
  240. $word .= $table[$r];
  241. if ($table == $vowels)
  242. $table = array_merge($cons, $midcons);
  243. else
  244. $table = $vowels;
  245. }
  246. $word = substr($word,0,$len);
  247. foreach ($noend as $noe) {
  248. if ((strlen($word) > 2) && (substr($word,-2) == $noe)) {
  249. $word = substr($word,0,-1);
  250. break;
  251. }
  252. }
  253. if (substr($word, -1) == 'q')
  254. $word = substr($word, 0, -1);
  255. return $word;
  256. }
  257. /**
  258. * @brief escape text ($str) for XML transport
  259. *
  260. * @param string $str
  261. * @return string Escaped text.
  262. */
  263. function xmlify($str) {
  264. $buffer = '';
  265. $len = mb_strlen($str);
  266. for($x = 0; $x < $len; $x ++) {
  267. $char = mb_substr($str,$x,1);
  268. switch( $char ) {
  269. case "\r" :
  270. break;
  271. case "&" :
  272. $buffer .= '&amp;';
  273. break;
  274. case "'" :
  275. $buffer .= '&apos;';
  276. break;
  277. case "\"" :
  278. $buffer .= '&quot;';
  279. break;
  280. case '<' :
  281. $buffer .= '&lt;';
  282. break;
  283. case '>' :
  284. $buffer .= '&gt;';
  285. break;
  286. case "\n" :
  287. $buffer .= "\n";
  288. break;
  289. default :
  290. $buffer .= $char;
  291. break;
  292. }
  293. }
  294. $buffer = trim($buffer);
  295. return($buffer);
  296. }
  297. // undo an xmlify
  298. // pass xml escaped text ($s), returns unescaped text
  299. function unxmlify($s) {
  300. $ret = str_replace('&amp;','&', $s);
  301. $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
  302. return $ret;
  303. }
  304. /**
  305. * Convenience wrapper, reverse the operation "bin2hex"
  306. * This is a built-in function in php >= 5.4
  307. *
  308. * @FIXME We already have php >= 5.4 requirements, so can we remove this?
  309. */
  310. if(! function_exists('hex2bin')) {
  311. function hex2bin($s) {
  312. if(! (is_string($s) && strlen($s)))
  313. return '';
  314. if(strlen($s) & 1) {
  315. logger('hex2bin: illegal hex string: ' . $s);
  316. return $s;
  317. }
  318. if(! ctype_xdigit($s)) {
  319. return($s);
  320. }
  321. return(pack("H*",$s));
  322. }}
  323. // Automatic pagination.
  324. // To use, get the count of total items.
  325. // Then call App::set_pager_total($number_items);
  326. // Optionally call App::set_pager_itemspage($n) to the number of items to display on each page
  327. // Then call paginate($a) after the end of the display loop to insert the pager block on the page
  328. // (assuming there are enough items to paginate).
  329. // When using with SQL, the setting LIMIT %d, %d => App::$pager['start'],App::$pager['itemspage']
  330. // will limit the results to the correct items for the current page.
  331. // The actual page handling is then accomplished at the application layer.
  332. function paginate(&$a) {
  333. $o = '';
  334. $stripped = preg_replace('/(&page=[0-9]*)/','',App::$query_string);
  335. // $stripped = preg_replace('/&zid=(.*?)([\?&]|$)/ism','',$stripped);
  336. $stripped = str_replace('q=','',$stripped);
  337. $stripped = trim($stripped,'/');
  338. $pagenum = App::$pager['page'];
  339. $url = z_root() . '/' . $stripped;
  340. if(App::$pager['total'] > App::$pager['itemspage']) {
  341. $o .= '<div class="pager">';
  342. if(App::$pager['page'] != 1)
  343. $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.(App::$pager['page'] - 1).'">' . t('prev') . '</a></span> ';
  344. $o .= "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
  345. $numpages = App::$pager['total'] / App::$pager['itemspage'];
  346. $numstart = 1;
  347. $numstop = $numpages;
  348. if($numpages > 14) {
  349. $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
  350. $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
  351. }
  352. for($i = $numstart; $i <= $numstop; $i++){
  353. if($i == App::$pager['page'])
  354. $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
  355. else
  356. $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
  357. $o .= '</span> ';
  358. }
  359. if((App::$pager['total'] % App::$pager['itemspage']) != 0) {
  360. if($i == App::$pager['page'])
  361. $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
  362. else
  363. $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
  364. $o .= '</span> ';
  365. }
  366. $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
  367. $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
  368. if((App::$pager['total'] - (App::$pager['itemspage'] * App::$pager['page'])) > 0)
  369. $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".(App::$pager['page'] + 1).'">' . t('next') . '</a></span>';
  370. $o .= '</div>'."\r\n";
  371. }
  372. return $o;
  373. }
  374. function alt_pager(&$a, $i, $more = '', $less = '') {
  375. if(! $more)
  376. $more = t('older');
  377. if(! $less)
  378. $less = t('newer');
  379. $stripped = preg_replace('/(&page=[0-9]*)/','',App::$query_string);
  380. $stripped = str_replace('q=','',$stripped);
  381. $stripped = trim($stripped,'/');
  382. //$pagenum = App::$pager['page'];
  383. $url = z_root() . '/' . $stripped;
  384. return replace_macros(get_markup_template('alt_pager.tpl'), array(
  385. '$has_less' => ((App::$pager['page'] > 1) ? true : false),
  386. '$has_more' => (($i > 0 && $i >= App::$pager['itemspage']) ? true : false),
  387. '$less' => $less,
  388. '$more' => $more,
  389. '$url' => $url,
  390. '$prevpage' => App::$pager['page'] - 1,
  391. '$nextpage' => App::$pager['page'] + 1,
  392. ));
  393. }
  394. /**
  395. * @brief Generate a guaranteed unique (for this domain) item ID for ATOM.
  396. *
  397. * Safe from birthday paradox.
  398. *
  399. * @return string a unique id
  400. */
  401. function item_message_id() {
  402. do {
  403. $dups = false;
  404. $hash = random_string();
  405. $mid = $hash . '@' . App::get_hostname();
  406. $r = q("SELECT id FROM item WHERE mid = '%s' LIMIT 1",
  407. dbesc($mid));
  408. if(count($r))
  409. $dups = true;
  410. } while($dups == true);
  411. return $mid;
  412. }
  413. /**
  414. * @brief Generate a guaranteed unique photo ID.
  415. *
  416. * Safe from birthday paradox.
  417. *
  418. * @return string a uniqe hash
  419. */
  420. function photo_new_resource() {
  421. do {
  422. $found = false;
  423. $resource = hash('md5', uniqid(mt_rand(), true));
  424. $r = q("SELECT id FROM photo WHERE resource_id = '%s' LIMIT 1",
  425. dbesc($resource));
  426. if(count($r))
  427. $found = true;
  428. } while($found === true);
  429. return $resource;
  430. }
  431. // for html,xml parsing - let's say you've got
  432. // an attribute foobar="class1 class2 class3"
  433. // and you want to find out if it contains 'class3'.
  434. // you can't use a normal sub string search because you
  435. // might match 'notclass3' and a regex to do the job is
  436. // possible but a bit complicated.
  437. // pass the attribute string as $attr and the attribute you
  438. // are looking for as $s - returns true if found, otherwise false
  439. function attribute_contains($attr, $s) {
  440. $a = explode(' ', $attr);
  441. if(count($a) && in_array($s, $a))
  442. return true;
  443. return false;
  444. }
  445. /**
  446. * @brief Logging function for Hubzilla.
  447. *
  448. * Logging output is configured through Hubzilla's system config. The log file
  449. * is set in system logfile, log level in system loglevel and to enable logging
  450. * set system debugging.
  451. *
  452. * Available constants for log level are LOGGER_NORMAL, LOGGER_TRACE, LOGGER_DEBUG,
  453. * LOGGER_DATA and LOGGER_ALL.
  454. *
  455. * Since PHP5.4 we get the file, function and line automatically where the logger
  456. * was called, so no need to add it to the message anymore.
  457. *
  458. * @param string $msg Message to log
  459. * @param int $level A log level.
  460. * @param int $priority - compatible with syslog
  461. */
  462. function logger($msg, $level = LOGGER_NORMAL, $priority = LOG_INFO) {
  463. if(App::$module == 'setup' && is_writable('install.log')) {
  464. $debugging = true;
  465. $logfile = 'install.log';
  466. $loglevel = LOGGER_ALL;
  467. }
  468. else {
  469. $debugging = get_config('system', 'debugging');
  470. $loglevel = intval(get_config('system', 'loglevel'));
  471. $logfile = get_config('system', 'logfile');
  472. }
  473. if((! $debugging) || (! $logfile) || ($level > $loglevel))
  474. return;
  475. $where = '';
  476. // We require > 5.4 but leave the version check so that install issues (including version) can be logged
  477. if(version_compare(PHP_VERSION, '5.4.0') >= 0) {
  478. $stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
  479. $where = basename($stack[0]['file']) . ':' . $stack[0]['line'] . ':' . $stack[1]['function'] . ': ';
  480. }
  481. $s = datetime_convert() . ':' . log_priority_str($priority) . ':' . session_id() . ':' . $where . $msg . PHP_EOL;
  482. $pluginfo = array('filename' => $logfile, 'loglevel' => $level, 'message' => $s,'priority' => $priority, 'logged' => false);
  483. if(! (App::$module == 'setup'))
  484. call_hooks('logger',$pluginfo);
  485. if(! $pluginfo['logged'])
  486. @file_put_contents($pluginfo['filename'], $pluginfo['message'], FILE_APPEND);
  487. }
  488. // like logger() but with a function backtrace to pinpoint certain classes
  489. // of problems which show up deep in the calling stack
  490. function btlogger($msg, $level = LOGGER_NORMAL, $priority = LOG_INFO) {
  491. logger($msg, $level, $priority);
  492. if(version_compare(PHP_VERSION, '5.4.0') >= 0) {
  493. $stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  494. if($stack) {
  495. for($x = 1; $x < count($stack); $x ++) {
  496. logger('stack: ' . basename($stack[$x]['file']) . ':' . $stack[$x]['line'] . ':' . $stack[$x]['function'] . '()',$level, $priority);
  497. }
  498. }
  499. }
  500. }
  501. function log_priority_str($priority) {
  502. $parr = array(
  503. LOG_EMERG => 'LOG_EMERG',
  504. LOG_ALERT => 'LOG_ALERT',
  505. LOG_CRIT => 'LOG_CRIT',
  506. LOG_ERR => 'LOG_ERR',
  507. LOG_WARNING => 'LOG_WARNING',
  508. LOG_NOTICE => 'LOG_NOTICE',
  509. LOG_INFO => 'LOG_INFO',
  510. LOG_DEBUG => 'LOG_DEBUG'
  511. );
  512. if($parr[$priority])
  513. return $parr[$priority];
  514. return 'LOG_UNDEFINED';
  515. }
  516. /**
  517. * @brief This is a special logging facility for developers.
  518. *
  519. * It allows one to target specific things to trace/debug and is identical to
  520. * logger() with the exception of the log filename. This allows one to isolate
  521. * specific calls while allowing logger() to paint a bigger picture of overall
  522. * activity and capture more detail.
  523. *
  524. * If you find dlogger() calls in checked in code, you are free to remove them -
  525. * so as to provide a noise-free development environment which responds to events
  526. * you are targetting personally.
  527. *
  528. * @param string $msg Message to log
  529. * @param int $level A log level.
  530. */
  531. function dlogger($msg, $level = 0) {
  532. // turn off logger in install mode
  533. global $a;
  534. global $db;
  535. if((App::$module == 'install') || (! (DBA::$dba && DBA::$dba->connected)))
  536. return;
  537. $debugging = get_config('system','debugging');
  538. $loglevel = intval(get_config('system','loglevel'));
  539. $logfile = get_config('system','dlogfile');
  540. if((! $debugging) || (! $logfile) || ($level > $loglevel))
  541. return;
  542. $where = '';
  543. if(version_compare(PHP_VERSION, '5.4.0') >= 0) {
  544. $stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
  545. $where = basename($stack[0]['file']) . ':' . $stack[0]['line'] . ':' . $stack[1]['function'] . ': ';
  546. }
  547. @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $where . $msg . PHP_EOL, FILE_APPEND);
  548. }
  549. function profiler($t1,$t2,$label) {
  550. if(file_exists('profiler.out') && $t1 && t2)
  551. @file_put_contents('profiler.out', sprintf('%01.4f %s',$t2 - $t1,$label) . PHP_EOL, FILE_APPEND);
  552. }
  553. function activity_match($haystack,$needle) {
  554. if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
  555. return true;
  556. return false;
  557. }
  558. // Pull out all #hashtags and @person tags from $s;
  559. // We also get @person@domain.com - which would make
  560. // the regex quite complicated as tags can also
  561. // end a sentence. So we'll run through our results
  562. // and strip the period from any tags which end with one.
  563. // Returns array of tags found, or empty array.
  564. function get_tags($s) {
  565. $ret = array();
  566. $match = array();
  567. // ignore anything in a code block
  568. $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
  569. // ignore anything in [style= ]
  570. $s = preg_replace('/\[style=(.*?)\]/sm','',$s);
  571. // ignore anything in [color= ], because it may contain color codes which are mistaken for tags
  572. $s = preg_replace('/\[color=(.*?)\]/sm','',$s);
  573. // match any double quoted tags
  574. if(preg_match_all('/([@#]\&quot\;.*?\&quot\;)/',$s,$match)) {
  575. foreach($match[1] as $mtch) {
  576. $ret[] = $mtch;
  577. }
  578. }
  579. // Match full names against @tags including the space between first and last
  580. // We will look these up afterward to see if they are full names or not recognisable.
  581. // The lookbehind is used to prevent a match in the middle of a word
  582. // '=' needs to be avoided because when the replacement is made (in handle_tag()) it has to be ignored there
  583. // Feel free to allow '=' if the issue with '=' is solved in handle_tag()
  584. // added / ? and [ to avoid issues with hashchars in url paths
  585. if(preg_match_all('/(?<![a-zA-Z0-9=\/\?])(@[^ \x0D\x0A,:?\[]+ [^ \x0D\x0A@,:?\[]+)/',$s,$match)) {
  586. foreach($match[1] as $mtch) {
  587. if(substr($mtch,-1,1) === '.')
  588. $ret[] = substr($mtch,0,-1);
  589. else
  590. $ret[] = $mtch;
  591. }
  592. }
  593. // Otherwise pull out single word tags. These can be @nickname, @first_last
  594. // and #hash tags.
  595. if(preg_match_all('/(?<![a-zA-Z0-9=\/\?])([@#][^ \x0D\x0A,;:?\[]+)/',$s,$match)) {
  596. foreach($match[1] as $mtch) {
  597. if(substr($mtch,-1,1) === '.')
  598. $mtch = substr($mtch,0,-1);
  599. // ignore strictly numeric tags like #1 or #^ bookmarks or ## double hash
  600. if((strpos($mtch,'#') === 0) && ( ctype_digit(substr($mtch,1)) || substr($mtch,1,1) === '^') || substr($mtch,1,1) === '#')
  601. continue;
  602. // or quote remnants from the quoted strings we already picked out earlier
  603. if(strpos($mtch,'&quot'))
  604. continue;
  605. $ret[] = $mtch;
  606. }
  607. }
  608. // bookmarks
  609. if(preg_match_all('/#\^\[(url|zrl)(.*?)\](.*?)\[\/(url|zrl)\]/',$s,$match,PREG_SET_ORDER)) {
  610. foreach($match as $mtch) {
  611. $ret[] = $mtch[0];
  612. }
  613. }
  614. // make sure the longer tags are returned first so that if two or more have common substrings
  615. // we'll replace the longest ones first. Otherwise the common substring would be found in
  616. // both strings and the string replacement would link both to the shorter strings and
  617. // fail to link the longer string. Hubzilla github issue #378
  618. usort($ret,'tag_sort_length');
  619. // logger('get_tags: ' . print_r($ret,true));
  620. return $ret;
  621. }
  622. function tag_sort_length($a,$b) {
  623. if(mb_strlen($a) == mb_strlen($b))
  624. return 0;
  625. return((mb_strlen($b) < mb_strlen($a)) ? (-1) : 1);
  626. }
  627. function strip_zids($s) {
  628. return preg_replace('/[\?&]zid=(.*?)(&|$)/ism','$2',$s);
  629. }
  630. // quick and dirty quoted_printable encoding
  631. function qp($s) {
  632. return str_replace ("%","=",rawurlencode($s));
  633. }
  634. function get_mentions($item,$tags) {
  635. $o = '';
  636. if(! count($tags))
  637. return $o;
  638. foreach($tags as $x) {
  639. if($x['type'] == TERM_MENTION) {
  640. $o .= "\t\t" . '<link rel="mentioned" href="' . $x['url'] . '" />' . "\r\n";
  641. $o .= "\t\t" . '<link rel="ostatus:attention" href="' . $x['url'] . '" />' . "\r\n";
  642. }
  643. }
  644. return $o;
  645. }
  646. function contact_block() {
  647. $o = '';
  648. $a = get_app();
  649. if(! App::$profile['uid'])
  650. return;
  651. if(! perm_is_allowed(App::$profile['uid'],get_observer_hash(),'view_contacts'))
  652. return;
  653. $shown = get_pconfig(App::$profile['uid'],'system','display_friend_count');
  654. if($shown === false)
  655. $shown = 25;
  656. if($shown == 0)
  657. return;
  658. $is_owner = ((local_channel() && local_channel() == App::$profile['uid']) ? true : false);
  659. $sql_extra = '';
  660. $abook_flags = " and abook_pending = 0 and abook_self = 0 ";
  661. if(! $is_owner) {
  662. $abook_flags .= " and abook_hidden = 0 ";
  663. $sql_extra = " and xchan_hidden = 0 ";
  664. }
  665. if((! is_array(App::$profile)) || (App::$profile['hide_friends']))
  666. return $o;
  667. $r = q("SELECT COUNT(abook_id) AS total FROM abook left join xchan on abook_xchan = xchan_hash WHERE abook_channel = %d
  668. $abook_flags and xchan_orphan = 0 and xchan_deleted = 0 $sql_extra",
  669. intval(App::$profile['uid'])
  670. );
  671. if(count($r)) {
  672. $total = intval($r[0]['total']);
  673. }
  674. if(! $total) {
  675. $contacts = t('No connections');
  676. $micropro = null;
  677. } else {
  678. $randfunc = db_getfunc('RAND');
  679. $r = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash WHERE abook_channel = %d $abook_flags and abook_archived = 0 and xchan_orphan = 0 and xchan_deleted = 0 $sql_extra ORDER BY $randfunc LIMIT %d",
  680. intval(App::$profile['uid']),
  681. intval($shown)
  682. );
  683. if(count($r)) {
  684. $contacts = t('Connections');
  685. $micropro = Array();
  686. foreach($r as $rr) {
  687. $rr['archived'] = (intval($rr['abook_archived']) ? true : false);
  688. $micropro[] = micropro($rr,true,'mpfriend');
  689. }
  690. }
  691. }
  692. $tpl = get_markup_template('contact_block.tpl');
  693. $o = replace_macros($tpl, array(
  694. '$contacts' => $contacts,
  695. '$nickname' => App::$profile['channel_address'],
  696. '$viewconnections' => (($total > $shown) ? sprintf(t('View all %s connections'),$total) : ''),
  697. '$micropro' => $micropro,
  698. ));
  699. $arr = array('contacts' => $r, 'output' => $o);
  700. call_hooks('contact_block_end', $arr);
  701. return $o;
  702. }
  703. function chanlink_hash($s) {
  704. return z_root() . '/chanview?f=&hash=' . urlencode($s);
  705. }
  706. function chanlink_url($s) {
  707. return z_root() . '/chanview?f=&url=' . urlencode($s);
  708. }
  709. function chanlink_cid($d) {
  710. return z_root() . '/chanview?f=&cid=' . intval($d);
  711. }
  712. function magiclink_url($observer,$myaddr,$url) {
  713. return (($observer)
  714. ? z_root() . '/magic?f=&dest=' . $url . '&addr=' . $myaddr
  715. : $url
  716. );
  717. }
  718. function micropro($contact, $redirect = false, $class = '', $textmode = false) {
  719. if($contact['click'])
  720. $url = '#';
  721. else
  722. $url = chanlink_hash($contact['xchan_hash']);
  723. return replace_macros(get_markup_template(($textmode)?'micropro_txt.tpl':'micropro_img.tpl'),array(
  724. '$click' => (($contact['click']) ? $contact['click'] : ''),
  725. '$class' => $class . (($contact['archived']) ? ' archived' : ''),
  726. '$url' => $url,
  727. '$photo' => $contact['xchan_photo_s'],
  728. '$name' => $contact['xchan_name'],
  729. '$title' => $contact['xchan_name'] . ' [' . $contact['xchan_addr'] . ']',
  730. ));
  731. }
  732. function search($s,$id='search-box',$url='/search',$save = false) {
  733. $a = get_app();
  734. return replace_macros(get_markup_template('searchbox.tpl'),array(
  735. '$s' => $s,
  736. '$id' => $id,
  737. '$action_url' => z_root() . $url,
  738. '$search_label' => t('Search'),
  739. '$save_label' => t('Save'),
  740. '$savedsearch' => feature_enabled(local_channel(),'savedsearch')
  741. ));
  742. }
  743. function searchbox($s,$id='search-box',$url='/search',$save = false) {
  744. return replace_macros(get_markup_template('searchbox.tpl'),array(
  745. '$s' => $s,
  746. '$id' => $id,
  747. '$action_url' => z_root() . '/' . $url,
  748. '$search_label' => t('Search'),
  749. '$save_label' => t('Save'),
  750. '$savedsearch' => feature_enabled(local_channel(),'savedsearch')
  751. ));
  752. }
  753. function valid_email_regex($x){
  754. if(preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
  755. return true;
  756. return false;
  757. }
  758. function valid_email($x){
  759. if(get_config('system','disable_email_validation'))
  760. return true;
  761. return valid_email_regex($x);
  762. }
  763. /**
  764. * @brief Replace naked text hyperlink with HTML formatted hyperlink.
  765. *
  766. * @param string $s
  767. * @param boolean $me (optional) default false
  768. * @return string
  769. */
  770. function linkify($s, $me = false) {
  771. $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\@\~\#\'\%\$\!\+\,\@]*)/", (($me) ? ' <a href="$1" rel="me" >$1</a>' : ' <a href="$1" >$1</a>'), $s);
  772. $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$s);
  773. return($s);
  774. }
  775. /**
  776. * @brief Replace media element using http url with https to a local redirector
  777. * if using https locally.
  778. *
  779. * Looks for HTML tags containing src elements that are http when we're viewing an https page
  780. * Typically this throws an insecure content violation in the browser. So we redirect them
  781. * to a local redirector which uses https and which redirects to the selected content
  782. *
  783. * @param string $s
  784. * @returns string
  785. */
  786. function sslify($s) {
  787. if (strpos(z_root(),'https:') === false)
  788. return $s;
  789. // By default we'll only sslify img tags because media files will probably choke.
  790. // You can set sslify_everything if you want - but it will likely white-screen if it hits your php memory limit.
  791. // The downside is that http: media files will likely be blocked by your browser
  792. // Complain to your browser maker
  793. $allow = get_config('system','sslify_everything');
  794. $pattern = (($allow) ? "/\<(.*?)src=\"(http\:.*?)\"(.*?)\>/" : "/\<img(.*?)src=\"(http\:.*?)\"(.*?)\>/" );
  795. $matches = null;
  796. $cnt = preg_match_all($pattern,$s,$matches,PREG_SET_ORDER);
  797. if ($cnt) {
  798. foreach ($matches as $match) {
  799. $filename = basename( parse_url($match[2], PHP_URL_PATH) );
  800. $s = str_replace($match[2],z_root() . '/sslify/' . $filename . '?f=&url=' . urlencode($match[2]),$s);
  801. }
  802. }
  803. return $s;
  804. }
  805. function get_poke_verbs() {
  806. // index is present tense verb
  807. // value is array containing past tense verb, translation of present, translation of past
  808. if(get_config('system','poke_basic')) {
  809. $arr = array(
  810. 'poke' => array( 'poked', t('poke'), t('poked')),
  811. );
  812. }
  813. else {
  814. $arr = array(
  815. 'poke' => array( 'poked', t('poke'), t('poked')),
  816. 'ping' => array( 'pinged', t('ping'), t('pinged')),
  817. 'prod' => array( 'prodded', t('prod'), t('prodded')),
  818. 'slap' => array( 'slapped', t('slap'), t('slapped')),
  819. 'finger' => array( 'fingered', t('finger'), t('fingered')),
  820. 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')),
  821. );
  822. call_hooks('poke_verbs', $arr);
  823. }
  824. return $arr;
  825. }
  826. function get_mood_verbs() {
  827. $arr = array(
  828. 'happy' => t('happy'),
  829. 'sad' => t('sad'),
  830. 'mellow' => t('mellow'),
  831. 'tired' => t('tired'),
  832. 'perky' => t('perky'),
  833. 'angry' => t('angry'),
  834. 'stupefied' => t('stupefied'),
  835. 'puzzled' => t('puzzled'),
  836. 'interested' => t('interested'),
  837. 'bitter' => t('bitter'),
  838. 'cheerful' => t('cheerful'),
  839. 'alive' => t('alive'),
  840. 'annoyed' => t('annoyed'),
  841. 'anxious' => t('anxious'),
  842. 'cranky' => t('cranky'),
  843. 'disturbed' => t('disturbed'),
  844. 'frustrated' => t('frustrated'),
  845. 'depressed' => t('depressed'),
  846. 'motivated' => t('motivated'),
  847. 'relaxed' => t('relaxed'),
  848. 'surprised' => t('surprised'),
  849. );
  850. call_hooks('mood_verbs', $arr);
  851. return $arr;
  852. }
  853. // Function to list all smilies, both internal and from addons
  854. // Returns array with keys 'texts' and 'icons'
  855. function list_smilies() {
  856. $a = get_app();
  857. $texts = array(
  858. '&lt;3',
  859. '&lt;/3',
  860. '&lt;\\3',
  861. ':-)',
  862. ';-)',
  863. ':-(',
  864. ':-P',
  865. ':-p',
  866. ':-"',
  867. ':-&quot;',
  868. ':-x',
  869. ':-X',
  870. ':-D',
  871. '8-|',
  872. '8-O',
  873. ':-O',
  874. '\\o/',
  875. 'o.O',
  876. 'O.o',
  877. 'o_O',
  878. 'O_o',
  879. ":'(",
  880. ":-!",
  881. ":-/",
  882. ":-[",
  883. "8-)",
  884. ':beer',
  885. ':homebrew',
  886. ':coffee',
  887. ':facepalm',
  888. ':like',
  889. ':dislike',
  890. 'red#matrix',
  891. 'red#',
  892. 'r#'
  893. );
  894. $icons = array(
  895. '<img class="smiley" src="' . z_root() . '/images/smiley-heart.gif" alt="&lt;3" />',
  896. '<img class="smiley" src="' . z_root() . '/images/smiley-brokenheart.gif" alt="&lt;/3" />',
  897. '<img class="smiley" src="' . z_root() . '/images/smiley-brokenheart.gif" alt="&lt;\\3" />',
  898. '<img class="smiley" src="' . z_root() . '/images/smiley-smile.gif" alt=":-)" />',
  899. '<img class="smiley" src="' . z_root() . '/images/smiley-wink.gif" alt=";-)" />',
  900. '<img class="smiley" src="' . z_root() . '/images/smiley-frown.gif" alt=":-(" />',
  901. '<img class="smiley" src="' . z_root() . '/images/smiley-tongue-out.gif" alt=":-P" />',
  902. '<img class="smiley" src="' . z_root() . '/images/smiley-tongue-out.gif" alt=":-p" />',
  903. '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-\"" />',
  904. '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-\"" />',
  905. '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-x" />',
  906. '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-X" />',
  907. '<img class="smiley" src="' . z_root() . '/images/smiley-laughing.gif" alt=":-D" />',
  908. '<img class="smiley" src="' . z_root() . '/images/smiley-surprised.gif" alt="8-|" />',
  909. '<img class="smiley" src="' . z_root() . '/images/smiley-surprised.gif" alt="8-O" />',
  910. '<img class="smiley" src="' . z_root() . '/images/smiley-surprised.gif" alt=":-O" />',
  911. '<img class="smiley" src="' . z_root() . '/images/smiley-thumbsup.gif" alt="\\o/" />',
  912. '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="o.O" />',
  913. '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="O.o" />',
  914. '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="o_O" />',
  915. '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="O_o" />',
  916. '<img class="smiley" src="' . z_root() . '/images/smiley-cry.gif" alt=":\'(" />',
  917. '<img class="smiley" src="' . z_root() . '/images/smiley-foot-in-mouth.gif" alt=":-!" />',
  918. '<img class="smiley" src="' . z_root() . '/images/smiley-undecided.gif" alt=":-/" />',
  919. '<img class="smiley" src="' . z_root() . '/images/smiley-embarassed.gif" alt=":-[" />',
  920. '<img class="smiley" src="' . z_root() . '/images/smiley-cool.gif" alt="8-)" />',
  921. '<img class="smiley" src="' . z_root() . '/images/beer_mug.gif" alt=":beer" />',
  922. '<img class="smiley" src="' . z_root() . '/images/beer_mug.gif" alt=":homebrew" />',
  923. '<img class="smiley" src="' . z_root() . '/images/coffee.gif" alt=":coffee" />',
  924. '<img class="smiley" src="' . z_root() . '/images/smiley-facepalm.gif" alt=":facepalm" />',
  925. '<img class="smiley" src="' . z_root() . '/images/like.gif" alt=":like" />',
  926. '<img class="smiley" src="' . z_root() . '/images/dislike.gif" alt=":dislike" />',
  927. '<a href="http://getzot.com"><strong>red<img class="smiley bb_rm-logo" src="' . z_root() . '/images/rm-32.png" alt="' . urlencode('red#matrix') . '" />matrix</strong></a>',
  928. '<a href="http://getzot.com"><strong>red<img class="smiley bb_rm-logo" src="' . z_root() . '/images/rm-32.png" alt="' . urlencode('red#') . '" />matrix</strong></a>',
  929. '<a href="http://getzot.com"><strong>red<img class="smiley bb_rm-logo" src="' . z_root() . '/images/rm-32.png" alt="r#" />matrix</strong></a>'
  930. );
  931. $params = array('texts' => $texts, 'icons' => $icons);
  932. call_hooks('smilie', $params);
  933. return $params;
  934. }
  935. /**
  936. * @brief Replaces text emoticons with graphical images.
  937. *
  938. * It is expected that this function will be called using HTML text.
  939. * We will escape text between HTML pre and code blocks, and HTML attributes
  940. * (such as urls) from being processed.
  941. *
  942. * At a higher level, the bbcode [nosmile] tag can be used to prevent this
  943. * function from being executed by the prepare_text() routine when preparing
  944. * bbcode source for HTML display.
  945. *
  946. * @param string $s
  947. * @param boolean $sample (optional) default false
  948. * @return string
  949. */
  950. function smilies($s, $sample = false) {
  951. if(intval(get_config('system', 'no_smilies'))
  952. || (local_channel() && intval(get_pconfig(local_channel(), 'system', 'no_smilies'))))
  953. return $s;
  954. $s = preg_replace_callback('{<(pre|code)>.*?</\1>}ism', 'smile_shield', $s);
  955. $s = preg_replace_callback('/<[a-z]+ .*?>/ism', 'smile_shield', $s);
  956. $params = list_smilies();
  957. $params['string'] = $s;
  958. if ($sample) {
  959. $s = '<div class="smiley-sample">';
  960. for ($x = 0; $x < count($params['texts']); $x ++) {
  961. $s .= '<dl><dt>' . $params['texts'][$x] . '</dt><dd>' . $params['icons'][$x] . '</dd></dl>';
  962. }
  963. } else {
  964. $params['string'] = preg_replace_callback('/&lt;(3+)/','preg_heart',$params['string']);
  965. $s = str_replace($params['texts'],$params['icons'],$params['string']);
  966. }
  967. $s = preg_replace_callback('/<!--base64:(.*?)-->/ism', 'smile_unshield', $s);
  968. return $s;
  969. }
  970. /**
  971. * @brief
  972. *
  973. * @param array $m
  974. * @return string
  975. */
  976. function smile_shield($m) {
  977. return '<!--base64:' . base64url_encode($m[0]) . '-->';
  978. }
  979. function smile_unshield($m) {
  980. return base64url_decode($m[1]);
  981. }
  982. /**
  983. * @brief Expand <3333 to the correct number of hearts.
  984. *
  985. * @param array $x
  986. */
  987. function preg_heart($x) {
  988. $a = get_app();
  989. if (strlen($x[1]) == 1)
  990. return $x[0];
  991. $t = '';
  992. for($cnt = 0; $cnt < strlen($x[1]); $cnt ++)
  993. $t .= '<img class="smiley" src="' . z_root() . '/images/smiley-heart.gif" alt="&lt;3" />';
  994. $r = str_replace($x[0],$t,$x[0]);
  995. return $r;
  996. }
  997. function day_translate($s) {
  998. $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
  999. array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
  1000. $s);
  1001. $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
  1002. 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')),
  1003. $ret);
  1004. return $ret;
  1005. }
  1006. /**
  1007. * @brief normalises a string.
  1008. *
  1009. * @param string $url
  1010. * @return string
  1011. */
  1012. function normalise_link($url) {
  1013. $ret = str_replace(array('https:', '//www.'), array('http:', '//'), $url);
  1014. return(rtrim($ret, '/'));
  1015. }
  1016. /**
  1017. * @brief Compare two URLs to see if they are the same.
  1018. *
  1019. * But ignore slight but hopefully insignificant differences such as if one
  1020. * is https and the other isn't, or if one is www.something and the other
  1021. * isn't - and also ignore case differences.
  1022. *
  1023. * @see normalis_link()
  1024. *
  1025. * @param string $a
  1026. * @param string $b
  1027. * @return true if the URLs match, otherwise false
  1028. */
  1029. function link_compare($a, $b) {
  1030. if (strcasecmp(normalise_link($a), normalise_link($b)) === 0)
  1031. return true;
  1032. return false;
  1033. }
  1034. // Given an item array, convert the body element from bbcode to html and add smilie icons.
  1035. // If attach is true, also add icons for item attachments
  1036. function unobscure(&$item) {
  1037. if(array_key_exists('item_obscured',$item) && intval($item['item_obscured'])) {
  1038. $key = get_config('system','prvkey');
  1039. if($item['title'])
  1040. $item['title'] = crypto_unencapsulate(json_decode_plus($item['title']),$key);
  1041. if($item['body'])
  1042. $item['body'] = crypto_unencapsulate(json_decode_plus($item['body']),$key);
  1043. if(get_config('system','item_cache')) {
  1044. q("update item set title = '%s', body = '%s', item_obscured = 0 where id = %d",
  1045. dbesc($item['title']),
  1046. dbesc($item['body']),
  1047. intval($item['id'])
  1048. );
  1049. }
  1050. }
  1051. }
  1052. function unobscure_mail(&$item) {
  1053. if(array_key_exists('mail_obscured',$item) && intval($item['mail_obscured'])) {
  1054. if($item['title'])
  1055. $item['title'] = base64url_decode(str_rot47($item['title']));
  1056. if($item['body'])
  1057. $item['body'] = base64url_decode(str_rot47($item['body']));
  1058. }
  1059. }
  1060. function theme_attachments(&$item) {
  1061. $arr = json_decode_plus($item['attach']);
  1062. if(is_array($arr) && count($arr)) {
  1063. $attaches = array();
  1064. foreach($arr as $r) {
  1065. $icon = getIconFromType($r['type']);
  1066. $label = (($r['title']) ? urldecode(htmlspecialchars($r['title'], ENT_COMPAT, 'UTF-8')) : t('Unknown Attachment'));
  1067. //some feeds provide an attachment where title an empty space
  1068. if($label == ' ')
  1069. $label = t('Unknown Attachment');
  1070. $title = t('Size') . ' ' . (($r['length']) ? userReadableSize($r['length']) : t('unknown'));
  1071. require_once('include/channel.php');
  1072. if(is_foreigner($item['author_xchan']))
  1073. $url = $r['href'];
  1074. else
  1075. $url = z_root() . '/magic?f=&hash=' . $item['author_xchan'] . '&dest=' . $r['href'] . '/' . $r['revision'];
  1076. //$s .= '<a href="' . $url . '" title="' . $title . '" class="attachlink" >' . $icon . '</a>';
  1077. $attaches[] = array('label' => $label, 'url' => $url, 'icon' => $icon, 'title' => $title);
  1078. }
  1079. $s = replace_macros(get_markup_template('item_attach.tpl'), array(
  1080. '$attaches' => $attaches
  1081. ));
  1082. }
  1083. return $s;
  1084. }
  1085. function format_categories(&$item,$writeable) {
  1086. $s = '';
  1087. $terms = get_terms_oftype($item['term'],TERM_CATEGORY);
  1088. if($terms) {
  1089. $categories = array();
  1090. foreach($terms as $t) {
  1091. $term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8',false) ;
  1092. if(! trim($term))
  1093. continue;
  1094. $removelink = (($writeable) ? z_root() . '/filerm/' . $item['id'] . '?f=&cat=' . urlencode($t['term']) : '');
  1095. $categories[] = array('term' => $term, 'writeable' => $writeable, 'removelink' => $removelink, 'url' => zid($t['url']));
  1096. }
  1097. $s = replace_macros(get_markup_template('item_categories.tpl'),array(
  1098. '$remove' => t('remove category'),
  1099. '$categories' => $categories
  1100. ));
  1101. }
  1102. return $s;
  1103. }
  1104. /**
  1105. * @brief Add any hashtags which weren't mentioned in the message body, e.g. community tags
  1106. *
  1107. * @param[in] array &$item
  1108. * @return string HTML link of hashtag
  1109. */
  1110. function format_hashtags(&$item) {
  1111. $s = '';
  1112. $terms = get_terms_oftype($item['term'], array(TERM_HASHTAG,TERM_COMMUNITYTAG));
  1113. if($terms) {
  1114. foreach($terms as $t) {
  1115. $term = htmlspecialchars($t['term'], ENT_COMPAT, 'UTF-8', false) ;
  1116. if(! trim($term))
  1117. continue;
  1118. if(strpos($item['body'], $t['url']))
  1119. continue;
  1120. if($s)
  1121. $s .= '&nbsp';
  1122. $s .= '#<a href="' . zid($t['url']) . '" >' . $term . '</a>';
  1123. }
  1124. }
  1125. return $s;
  1126. }
  1127. function format_mentions(&$item) {
  1128. $s = '';
  1129. $terms = get_terms_oftype($item['term'],TERM_MENTION);
  1130. if($terms) {
  1131. foreach($terms as $t) {
  1132. $term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8',false) ;
  1133. if(! trim($term))
  1134. continue;
  1135. if(strpos($item['body'], $t['url']))
  1136. continue;
  1137. if($s)
  1138. $s .= '&nbsp';
  1139. $s .= '@<a href="' . zid($t['url']) . '" >' . $term . '</a>';
  1140. }
  1141. }
  1142. return $s;
  1143. }
  1144. function format_filer(&$item) {
  1145. $s = '';
  1146. $terms = get_terms_oftype($item['term'],TERM_FILE);
  1147. if($terms) {
  1148. $categories = array();
  1149. foreach($terms as $t) {
  1150. $term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8',false) ;
  1151. if(! trim($term))
  1152. continue;
  1153. $removelink = z_root() . '/filerm/' . $item['id'] . '?f=&term=' . urlencode($t['term']);
  1154. $categories[] = array('term' => $term, 'removelink' => $removelink);
  1155. }
  1156. $s = replace_macros(get_markup_template('item_filer.tpl'),array(
  1157. '$remove' => t('remove from file'),
  1158. '$categories' => $categories
  1159. ));
  1160. }
  1161. return $s;
  1162. }
  1163. function generate_map($coord) {
  1164. $coord = trim($coord);
  1165. $coord = str_replace(array(',','/',' '),array(' ',' ',' '),$coord);
  1166. $arr = array('lat' => trim(substr($coord,0,strpos($coord,' '))), 'lon' => trim(substr($coord,strpos($coord,' ')+1)), 'html' => '');
  1167. call_hooks('generate_map',$arr);
  1168. return (($arr['html']) ? $arr['html'] : $coord);
  1169. }
  1170. function generate_named_map($location) {
  1171. $arr = array('location' => $location, 'html' => '');
  1172. call_hooks('generate_named_map',$arr);
  1173. return (($arr['html']) ? $arr['html'] : $location);
  1174. }
  1175. function format_event($jobject) {
  1176. $event = array();
  1177. $object = json_decode($jobject,true);
  1178. //ensure compatibility with older items - this check can be removed at a later point
  1179. if(array_key_exists('description', $object)) {
  1180. $bd_format = t('l F d, Y \@ g:i A'); // Friday January 18, 2011 @ 8:01 AM
  1181. $event['header'] = replace_macros(get_markup_template('event_item_header.tpl'),array(
  1182. '$title' => bbcode($object['title']),
  1183. '$dtstart_label' => t('Starts:'),
  1184. '$dtstart_title' => datetime_convert('UTC', 'UTC', $object['start'], (($object['adjust']) ? ATOM_TIME : 'Y-m-d\TH:i:s' )),
  1185. '$dtstart_dt' => (($object['adjust']) ? day_translate(datetime_convert('UTC', date_default_timezone_get(), $object['start'] , $bd_format )) : day_translate(datetime_convert('UTC', 'UTC', $object['start'] , $bd_format))),
  1186. '$finish' => (($object['nofinish']) ? false : true),
  1187. '$dtend_label' => t('Finishes:'),
  1188. '$dtend_title' => datetime_convert('UTC','UTC',$object['finish'], (($object['adjust']) ? ATOM_TIME : 'Y-m-d\TH:i:s' )),
  1189. '$dtend_dt' => (($object['adjust']) ? day_translate(datetime_convert('UTC', date_default_timezone_get(), $object['finish'] , $bd_format )) : day_translate(datetime_convert('UTC', 'UTC', $object['finish'] , $bd_format )))
  1190. ));
  1191. $event['content'] = replace_macros(get_markup_template('event_item_content.tpl'),array(
  1192. '$description' => bbcode($object['description']),
  1193. '$location_label' => t('Location:'),
  1194. '$location' => bbcode($object['location'])
  1195. ));
  1196. }
  1197. return $event;
  1198. }
  1199. function prepare_body(&$item,$attach = false) {
  1200. require_once('include/channel.php');
  1201. call_hooks('prepare_body_init', $item);
  1202. $s = '';
  1203. $photo = '';
  1204. $is_photo = ((($item['verb'] === ACTIVITY_POST) && ($item['obj_type'] === ACTIVITY_OBJ_PHOTO)) ? true : false);
  1205. if($is_photo) {
  1206. $object = json_decode($item['object'],true);
  1207. // if original photo width is <= 640px prepend it to item body
  1208. if($object['link'][0]['width'] && $object['link'][0]['width'] <= 640) {
  1209. $s .= '<div class="inline-photo-item-wrapper"><a href="' . zid(rawurldecode($object['id'])) . '" target="_blank"><img class="inline-photo-item" style="max-width:' . $object['link'][0]['width'] . 'px; width:100%; height:auto;" src="' . zid(rawurldecode($object['link'][0]['href'])) . '"></a></div>' . $s;
  1210. }
  1211. // if original photo width is > 640px make it a cover photo
  1212. if($object['link'][0]['width'] && $object['link'][0]['width'] > 640) {
  1213. $scale = ((($object['link'][1]['width'] == 1024) || ($object['link'][1]['height'] == 1024)) ? 1 : 0);
  1214. $photo = '<a href="' . zid(rawurldecode($object['id'])) . '" target="_blank"><img style="max-width:' . $object['link'][$scale]['width'] . 'px; width:100%; height:auto;" src="' . zid(rawurldecode($object['link'][$scale]['href'])) . '"></a>';
  1215. }
  1216. }
  1217. $s .= prepare_text($item['body'],$item['mimetype'], false);
  1218. $event = (($item['obj_type'] === ACTIVITY_OBJ_EVENT) ? format_event($item['object']) : false);
  1219. $prep_arr = array(
  1220. 'item' => $item,
  1221. 'html' => $event ? $event['content'] : $s,
  1222. 'event' => $event['header'],
  1223. 'photo' => $photo
  1224. );
  1225. call_hooks('prepare_body', $prep_arr);
  1226. $s = $prep_arr['html'];
  1227. $photo = $prep_arr['photo'];
  1228. $event = $prep_arr['event'];
  1229. // q("update item set html = '%s' where id = %d",
  1230. // dbesc($s),
  1231. // intval($item['id'])
  1232. // );
  1233. if(! $attach) {
  1234. return $s;
  1235. }
  1236. if(strpos($s,'<div class="map">') !== false && $item['coord']) {
  1237. $x = generate_map(trim($item['coord']));
  1238. if($x) {
  1239. $s = preg_replace('/\<div class\=\"map\"\>/','$0' . $x,$s);
  1240. }
  1241. }
  1242. $attachments = theme_attachments($item);
  1243. $writeable = ((get_observer_hash() == $item['owner_xchan']) ? true : false);
  1244. $tags = format_hashtags($item);
  1245. if($item['resource_type'])
  1246. $mentions = format_mentions($item);
  1247. $categories = format_categories($item,$writeable);
  1248. if(local_channel() == $item['uid'])
  1249. $filer = format_filer($item);
  1250. $s = sslify($s);
  1251. $prep_arr = array(
  1252. 'item' => $item,
  1253. 'photo' => $photo,
  1254. 'html' => $s,
  1255. 'event' => $event,
  1256. 'categories' => $categories,
  1257. 'folders' => $filer,
  1258. 'tags' => $tags,
  1259. 'mentions' => $mentions,
  1260. 'attachments' => $attachments
  1261. );
  1262. call_hooks('prepare_body_final', $prep_arr);
  1263. unset($prep_arr['item']);
  1264. return $prep_arr;
  1265. }
  1266. /**
  1267. * @brief Given a text string, convert from bbcode to html and add smilie icons.
  1268. *
  1269. * @param string $text
  1270. * @param sting $content_type
  1271. * @return string
  1272. */
  1273. function prepare_text($text, $content_type = 'text/bbcode', $cache = false) {
  1274. switch($content_type) {
  1275. case 'text/plain':
  1276. $s = escape_tags($text);
  1277. break;
  1278. case 'text/html':
  1279. $s = $text;
  1280. break;
  1281. case 'text/markdown':
  1282. require_once('library/markdown.php');
  1283. $s = Markdown($text);
  1284. break;
  1285. case 'application/x-pdl';
  1286. $s = escape_tags($text);
  1287. break;
  1288. // No security checking is done here at display time - so we need to verify
  1289. // that the author is allowed to use PHP before storing. We also cannot allow
  1290. // importation of PHP text bodies from other sites. Therefore this content
  1291. // type is only valid for web pages (and profile details).
  1292. // It may be possible to provide a PHP message body which is evaluated on the
  1293. // sender's site before sending it elsewhere. In that case we will have a
  1294. // different content-type here.
  1295. case 'application/x-php':
  1296. ob_start();
  1297. eval($text);
  1298. $s = ob_get_contents();
  1299. ob_end_clean();
  1300. break;
  1301. case 'text/bbcode':
  1302. case '':
  1303. default:
  1304. require_once('include/bbcode.php');
  1305. if(stristr($text,'[nosmile]'))
  1306. $s = bbcode($text,false,true,$cache);
  1307. else
  1308. $s = smilies(bbcode($text,false,true,$cache));
  1309. $s = zidify_links($s);
  1310. break;
  1311. }
  1312. //logger('prepare_text: ' . $s);
  1313. return $s;
  1314. }
  1315. function create_export_photo_body(&$item) {
  1316. if(($item['verb'] === ACTIVITY_POST) && ($item['obj_type'] === ACTIVITY_OBJ_PHOTO)) {
  1317. $j = json_decode($item['object'],true);
  1318. if($j) {
  1319. $item['body'] .= "\n\n" . (($j['body']) ? $j['body'] : $j['bbcode']);
  1320. $item['sig'] = '';
  1321. }
  1322. }
  1323. }
  1324. /**
  1325. * zidify_callback() and zidify_links() work together to turn any HTML a tags with class="zrl" into zid links
  1326. * These will typically be generated by a bbcode '[zrl]' tag. This is done inside prepare_text() rather than bbcode()
  1327. * because the latter is used for general purpose conversions and the former is used only when preparing text for
  1328. * immediate display.
  1329. *
  1330. * Issues: Currently the order of HTML parameters in the text is somewhat rigid and inflexible.
  1331. * We assume it looks like \<a class="zrl" href="xxxxxxxxxx"\> and will not work if zrl and href appear in a different order.
  1332. *
  1333. * @param array $match
  1334. * @return string
  1335. */
  1336. function zidify_callback($match) {
  1337. $is_zid = ((feature_enabled(local_channel(),'sendzid')) || (strpos($match[1],'zrl')) ? true : false);
  1338. $replace = '<a' . $match[1] . ' href="' . (($is_zid) ? zid($match[2]) : $match[2]) . '"';
  1339. $x = str_replace($match[0],$replace,$match[0]);
  1340. return $x;
  1341. }
  1342. function zidify_img_callback($match) {
  1343. $is_zid = ((feature_enabled(local_channel(),'sendzid')) || (strpos($match[1],'zrl')) ? true : false);
  1344. $replace = '<img' . $match[1] . ' src="' . (($is_zid) ? zid($match[2]) : $match[2]) . '"';
  1345. $x = str_replace($match[0],$replace,$match[0]);
  1346. return $x;
  1347. }
  1348. function zidify_links($s) {
  1349. $s = preg_replace_callback('/\<a(.*?)href\=\"(.*?)\"/ism','zidify_callback',$s);
  1350. $s = preg_replace_callback('/\<img(.*?)src\=\"(.*?)\"/ism','zidify_img_callback',$s);
  1351. return $s;
  1352. }
  1353. /**
  1354. * @brief Return atom link elements for all of our hubs.
  1355. *
  1356. * @return string
  1357. */
  1358. function feed_hublinks() {
  1359. $hub = get_config('system', 'huburl');
  1360. $hubxml = '';
  1361. if(strlen($hub)) {
  1362. $hubs = explode(',', $hub);
  1363. if(count($hubs)) {
  1364. foreach($hubs as $h) {
  1365. $h = trim($h);
  1366. if(! strlen($h))
  1367. continue;
  1368. $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
  1369. }
  1370. }
  1371. }
  1372. return $hubxml;
  1373. }
  1374. /* return atom link elements for salmon endpoints */
  1375. function feed_salmonlinks($nick) {
  1376. $a = get_app();
  1377. $salmon = '<link rel="salmon" href="' . xmlify(z_root() . '/salmon/' . $nick) . '" />' . "\n" ;
  1378. // old style links that status.net still needed as of 12/2010
  1379. $salmon .= ' <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify(z_root() . '/salmon/' . $nick) . '" />' . "\n" ;
  1380. $salmon .= ' <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify(z_root() . '/salmon/' . $nick) . '" />' . "\n" ;
  1381. return $salmon;
  1382. }
  1383. function get_plink($item,$conversation_mode = true) {
  1384. if($conversation_mode)
  1385. $key = 'plink';
  1386. else
  1387. $key = 'llink';
  1388. if(x($item,$key)) {
  1389. return array(
  1390. 'href' => zid($item[$key]),
  1391. 'title' => t('Link to Source'),
  1392. );
  1393. }
  1394. else {
  1395. return false;
  1396. }
  1397. }
  1398. function unamp($s) {
  1399. return str_replace('&amp;', '&', $s);
  1400. }
  1401. function layout_select($channel_id, $current = '') {
  1402. $r = q("select mid,sid from item left join item_id on iid = item.id where service = 'PDL' and item.uid = item_id.uid and item_id.uid = %d and item_type = %d ",
  1403. intval($channel_id),
  1404. intval(ITEM_TYPE_PDL)
  1405. );
  1406. if($r) {
  1407. $empty_selected = (($current === false) ? ' selected="selected" ' : '');
  1408. $options .= '<option value="" ' . $empty_selected . '>' . t('default') . '</option>';
  1409. foreach($r as $rr) {
  1410. $selected = (($rr['mid'] == $current) ? ' selected="selected" ' : '');
  1411. $options .= '<option value="' . $rr['mid'] . '"' . $selected . '>' . $rr['sid'] . '</option>';
  1412. }
  1413. }
  1414. $o = replace_macros(get_markup_template('field_select_raw.tpl'), array(
  1415. '$field' => array('layout_mid', t('Page layout'), $selected, t('You can create your own with the layouts tool'), $options)
  1416. ));
  1417. return $o;
  1418. }
  1419. function mimetype_select($channel_id, $current = 'text/bbcode') {
  1420. $x = array(
  1421. 'text/bbcode',
  1422. 'text/html',
  1423. 'text/markdown',
  1424. 'text/plain',
  1425. 'application/x-pdl'
  1426. );
  1427. $a = get_app();
  1428. if(App::$is_sys) {
  1429. $x[] = 'application/x-php';
  1430. }
  1431. else {
  1432. $r = q("select account_id, account_roles, channel_pageflags from account left join channel on account_id = channel_account_id where
  1433. channel_id = %d limit 1",
  1434. intval($channel_id)
  1435. );
  1436. if($r) {
  1437. if(($r[0]['account_roles'] & ACCOUNT_ROLE_ALLOWCODE) || ($r[0]['channel_pageflags'] & PAGE_ALLOWCODE)) {
  1438. if(local_channel() && get_account_id() == $r[0]['account_id']) {
  1439. $x[] = 'application/x-php';
  1440. }
  1441. }
  1442. }
  1443. }
  1444. foreach($x as $y) {
  1445. $selected = (($y == $current) ? ' selected="selected" ' : '');
  1446. $options .= '<option name="' . $y . '"' . $selected . '>' . $y . '</option>';
  1447. }
  1448. $o = replace_macros(get_markup_template('field_select_raw.tpl'), array(
  1449. '$field' => array('mimetype', t('Page content type'), $selected, '', $options)
  1450. ));
  1451. return $o;
  1452. }
  1453. function lang_selector() {
  1454. global $a;
  1455. $langs = glob('view/*/hstrings.php');
  1456. $lang_options = array();
  1457. $selected = "";
  1458. if(is_array($langs) && count($langs)) {
  1459. $langs[] = '';
  1460. if(! in_array('view/en/hstrings.php',$langs))
  1461. $langs[] = 'view/en/';
  1462. asort($langs);
  1463. foreach($langs as $l) {
  1464. if($l == '') {
  1465. $lang_options[""] = t('default');
  1466. continue;
  1467. }
  1468. $ll = substr($l,5);
  1469. $ll = substr($ll,0,strrpos($ll,'/'));
  1470. $selected = (($ll === App::$language && (x($_SESSION, 'language'))) ? $ll : $selected);
  1471. $lang_options[$ll] = get_language_name($ll, $ll) . " ($ll)";
  1472. }
  1473. }
  1474. $tpl = get_markup_template("lang_selector.tpl");
  1475. $o = replace_macros($tpl, array(
  1476. '$title' => t('Select an alternate language'),
  1477. '$langs' => array($lang_options, $selected),
  1478. ));
  1479. return $o;
  1480. }
  1481. function engr_units_to_bytes ($size_str) {
  1482. if(! $size_str)
  1483. return $size_str;
  1484. switch (substr(trim($size_str), -1)) {
  1485. case 'M': case 'm': return (int)$size_str * 1048576;
  1486. case 'K': case 'k': return (int)$size_str * 1024;
  1487. case 'G': case 'g': return (int)$size_str * 1073741824;
  1488. default: return $size_str;
  1489. }
  1490. }
  1491. function base64url_encode($s, $strip_padding = true) {
  1492. $s = strtr(base64_encode($s),'+/','-_');
  1493. if($strip_padding)
  1494. $s = str_replace('=','',$s);
  1495. return $s;
  1496. }
  1497. function base64url_decode($s) {
  1498. if(is_array($s)) {
  1499. logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
  1500. return $s;
  1501. }
  1502. return base64_decode(strtr($s,'-_','+/'));
  1503. }
  1504. /**
  1505. * @ Return a div to clear floats.
  1506. *
  1507. * @return string
  1508. */
  1509. function cleardiv() {
  1510. return '<div class="clear"></div>';
  1511. }
  1512. function bb_translate_video($s) {
  1513. $arr = array('string' => $s);
  1514. call_hooks('bb_translate_video',$arr);
  1515. return $arr['string'];
  1516. }
  1517. function html2bb_video($s) {
  1518. $arr = array('string' => $s);
  1519. call_hooks('html2bb_video',$arr);
  1520. return $arr['string'];
  1521. }
  1522. /**
  1523. * apply xmlify() to all values of array $val, recursively
  1524. */
  1525. function array_xmlify($val) {
  1526. if (is_bool($val)) return $val?"true":"false";
  1527. if (is_array($val)) return array_map('array_xmlify', $val);
  1528. return xmlify((string) $val);
  1529. }
  1530. function reltoabs($text, $base) {
  1531. if (empty($base))
  1532. return $text;
  1533. $base = rtrim($base,'/');
  1534. $base2 = $base . "/";
  1535. // Replace links
  1536. $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
  1537. $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
  1538. $text = preg_replace($pattern, $replace, $text);
  1539. $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
  1540. $replace = "<a\${1} href=\"" . $base . "\${2}\"";
  1541. $text = preg_replace($pattern, $replace, $text);
  1542. // Replace images
  1543. $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
  1544. $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
  1545. $text = preg_replace($pattern, $replace, $text);
  1546. $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
  1547. $replace = "<img\${1} src=\"" . $base . "\${2}\"";
  1548. $text = preg_replace($pattern, $replace, $text);
  1549. // Done
  1550. return $text;
  1551. }
  1552. function item_post_type($item) {
  1553. switch($item['resource_type']) {
  1554. case 'photo':
  1555. $post_type = t('photo');
  1556. break;
  1557. case 'event':
  1558. $post_type = t('event');
  1559. break;
  1560. default:
  1561. $post_type = t('status');
  1562. if($item['mid'] != $item['parent_mid'])
  1563. $post_type = t('comment');
  1564. break;
  1565. }
  1566. if(strlen($item['verb']) && (! activity_match($item['verb'],ACTIVITY_POST)))
  1567. $post_type = t('activity');
  1568. return $post_type;
  1569. }
  1570. function undo_post_tagging($s) {
  1571. $matches = null;
  1572. $cnt = preg_match_all('/([@#])(\!*)\[zrl=(.*?)\](.*?)\[\/zrl\]/ism',$s,$matches,PREG_SET_ORDER);
  1573. if($cnt) {
  1574. foreach($matches as $mtch) {
  1575. $s = str_replace($mtch[0], $mtch[1] . $mtch[2] . str_replace(' ','_',$mtch[4]),$s);
  1576. }
  1577. }
  1578. return $s;
  1579. }
  1580. function fix_mce_lf($s) {
  1581. $s = str_replace("\r\n","\n",$s);
  1582. // $s = str_replace("\n\n","\n",$s);
  1583. return $s;
  1584. }
  1585. function protect_sprintf($s) {
  1586. return(str_replace('%','%%',$s));
  1587. }
  1588. function is_a_date_arg($s) {
  1589. $i = intval($s);
  1590. if($i > 1900) {
  1591. $y = date('Y');
  1592. if($i <= $y+1 && strpos($s,'-') == 4) {
  1593. $m = intval(substr($s,5));
  1594. if($m > 0 && $m <= 12)
  1595. return true;
  1596. }
  1597. }
  1598. return false;
  1599. }
  1600. function legal_webbie($s) {
  1601. if(! strlen($s))
  1602. return '';
  1603. $x = $s;
  1604. do {
  1605. $s = $x;
  1606. $x = preg_replace('/^([^a-z])(.*?)/',"$2",$s);
  1607. } while($x != $s);
  1608. return preg_replace('/([^a-z0-9\-\_])/','',$x);
  1609. }
  1610. function check_webbie($arr) {
  1611. $reservechan = get_config('system','reserved_channels');
  1612. if(strlen($reservechan))
  1613. $taken = explode(',', $reservechan);
  1614. else
  1615. $taken = array('principals','addressbooks','calendars');
  1616. $str = '';
  1617. if(count($arr)) {
  1618. foreach($arr as $x) {
  1619. $y = legal_webbie($x);
  1620. if(strlen($y)) {
  1621. if($str)
  1622. $str .= ',';
  1623. $str .= "'" . dbesc($y) . "'";
  1624. }
  1625. }
  1626. if(strlen($str)) {
  1627. $r = q("select channel_address from channel where channel_address in ( $str ) ");
  1628. if(count($r)) {
  1629. foreach($r as $rr) {
  1630. $taken[] = $rr['channel_address'];
  1631. }
  1632. }
  1633. foreach($arr as $x) {
  1634. $y = legal_webbie($x);
  1635. if(! in_array($y,$taken)) {
  1636. return $y;
  1637. }
  1638. }
  1639. }
  1640. }
  1641. return '';
  1642. }
  1643. function ids_to_array($arr,$idx = 'id') {
  1644. $t = array();
  1645. if($arr) {
  1646. foreach($arr as $x) {
  1647. if(! in_array($x[$idx],$t)) {
  1648. $t[] = $x[$idx];
  1649. }
  1650. }
  1651. }
  1652. return($t);
  1653. }
  1654. function ids_to_querystr($arr,$idx = 'id') {
  1655. $t = array();
  1656. if($arr) {
  1657. foreach($arr as $x) {
  1658. if(! in_array($x[$idx],$t)) {
  1659. $t[] = $x[$idx];
  1660. }
  1661. }
  1662. }
  1663. return(implode(',', $t));
  1664. }
  1665. // Fetches xchan and hubloc data for an array of items with only an
  1666. // author_xchan and owner_xchan. If $abook is true also include the abook info.
  1667. // This is needed in the API to save extra per item lookups there.
  1668. function xchan_query(&$items,$abook = true,$effective_uid = 0) {
  1669. $arr = array();
  1670. if($items && count($items)) {
  1671. if($effective_uid) {
  1672. for($x = 0; $x < count($items); $x ++) {
  1673. $items[$x]['real_uid'] = $items[$x]['uid'];
  1674. $items[$x]['uid'] = $effective_uid;
  1675. }
  1676. }
  1677. foreach($items as $item) {
  1678. if($item['owner_xchan'] && (! in_array($item['owner_xchan'],$arr)))
  1679. $arr[] = "'" . dbesc($item['owner_xchan']) . "'";
  1680. if($item['author_xchan'] && (! in_array($item['author_xchan'],$arr)))
  1681. $arr[] = "'" . dbesc($item['author_xchan']) . "'";
  1682. }
  1683. }
  1684. if(count($arr)) {
  1685. if($abook) {
  1686. $chans = q("select * from xchan left join hubloc on hubloc_hash = xchan_hash left join abook on abook_xchan = xchan_hash and abook_channel = %d
  1687. where xchan_hash in (" . protect_sprintf(implode(',', $arr)) . ") and hubloc_primary = 1",
  1688. intval($item['uid'])
  1689. );
  1690. }
  1691. else {
  1692. $chans = q("select xchan.*,hubloc.* from xchan left join hubloc on hubloc_hash = xchan_hash
  1693. where xchan_hash in (" . protect_sprintf(implode(',', $arr)) . ") and hubloc_primary = 1");
  1694. }
  1695. $xchans = q("select * from xchan where xchan_hash in (" . protect_sprintf(implode(',',$arr)) . ") and xchan_network in ('rss','unknown')");
  1696. if(! $chans)
  1697. $chans = $xchans;
  1698. else
  1699. $chans = array_merge($xchans,$chans);
  1700. }
  1701. if($items && count($items) && $chans && count($chans)) {
  1702. for($x = 0; $x < count($items); $x ++) {
  1703. $items[$x]['owner'] = find_xchan_in_array($items[$x]['owner_xchan'],$chans);
  1704. $items[$x]['author'] = find_xchan_in_array($items[$x]['author_xchan'],$chans);
  1705. }
  1706. }
  1707. }
  1708. function xchan_mail_query(&$item) {
  1709. $arr = array();
  1710. $chans = null;
  1711. if($item) {
  1712. if($item['from_xchan'] && (! in_array($item['from_xchan'],$arr)))
  1713. $arr[] = "'" . dbesc($item['from_xchan']) . "'";
  1714. if($item['to_xchan'] && (! in_array($item['to_xchan'],$arr)))
  1715. $arr[] = "'" . dbesc($item['to_xchan']) . "'";
  1716. }
  1717. if(count($arr)) {
  1718. $chans = q("select xchan.*,hubloc.* from xchan left join hubloc on hubloc_hash = xchan_hash
  1719. where xchan_hash in (" . implode(',', $arr) . ") and hubloc_primary = 1");
  1720. }
  1721. if($chans) {
  1722. $item['from'] = find_xchan_in_array($item['from_xchan'],$chans);
  1723. $item['to'] = find_xchan_in_array($item['to_xchan'],$chans);
  1724. }
  1725. }
  1726. function find_xchan_in_array($xchan,$arr) {
  1727. if(count($arr)) {
  1728. foreach($arr as $x) {
  1729. if($x['xchan_hash'] === $xchan) {
  1730. return $x;
  1731. }
  1732. }
  1733. }
  1734. return array();
  1735. }
  1736. function get_rel_link($j,$rel) {
  1737. if(is_array($j) && ($j))
  1738. foreach($j as $l)
  1739. if(array_key_exists('rel',$l) && $l['rel'] === $rel && array_key_exists('href',$l))
  1740. return $l['href'];
  1741. return '';
  1742. }
  1743. // Lots of code to write here
  1744. function magic_link($s) {
  1745. return $s;
  1746. }
  1747. // if $escape is true, dbesc() each element before adding quotes
  1748. function stringify_array_elms(&$arr,$escape = false) {
  1749. for($x = 0; $x < count($arr); $x ++)
  1750. $arr[$x] = "'" . (($escape) ? dbesc($arr[$x]) : $arr[$x]) . "'";
  1751. }
  1752. /**
  1753. * Indents a flat JSON string to make it more human-readable.
  1754. *
  1755. * @param string $json The original JSON string to process.
  1756. *
  1757. * @return string Indented version of the original JSON string.
  1758. */
  1759. function jindent($json) {
  1760. $result = '';
  1761. $pos = 0;
  1762. $strLen = strlen($json);
  1763. $indentStr = ' ';
  1764. $newLine = "\n";
  1765. $prevChar = '';
  1766. $outOfQuotes = true;
  1767. for ($i=0; $i<=$strLen; $i++) {
  1768. // Grab the next character in the string.
  1769. $char = substr($json, $i, 1);
  1770. // Are we inside a quoted string?
  1771. if ($char == '"' && $prevChar != '\\') {
  1772. $outOfQuotes = !$outOfQuotes;
  1773. // If this character is the end of an element,
  1774. // output a new line and indent the next line.
  1775. } else if(($char == '}' || $char == ']') && $outOfQuotes) {
  1776. $result .= $newLine;
  1777. $pos --;
  1778. for ($j=0; $j<$pos; $j++) {
  1779. $result .= $indentStr;
  1780. }
  1781. }
  1782. // Add the character to the result string.
  1783. $result .= $char;
  1784. // If the last character was the beginning of an element,
  1785. // output a new line and indent the next line.
  1786. if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) {
  1787. $result .= $newLine;
  1788. if ($char == '{' || $char == '[') {
  1789. $pos ++;
  1790. }
  1791. for ($j = 0; $j < $pos; $j++) {
  1792. $result .= $indentStr;
  1793. }
  1794. }
  1795. $prevChar = $char;
  1796. }
  1797. return $result;
  1798. }
  1799. function json_decode_plus($s) {
  1800. $x = json_decode($s,true);
  1801. if(! $x)
  1802. $x = json_decode(str_replace(array('\\"','\\\\'),array('"','\\'),$s),true);
  1803. return $x;
  1804. }
  1805. /**
  1806. * @brief Creates navigation menu for webpage, layout, blocks, menu sites.
  1807. *
  1808. * @return string
  1809. */
  1810. function design_tools() {
  1811. $channel = App::get_channel();
  1812. $sys = false;
  1813. if(App::$is_sys && is_site_admin()) {
  1814. require_once('include/channel.php');
  1815. $channel = get_sys_channel();
  1816. $sys = true;
  1817. }
  1818. $who = $channel['channel_address'];
  1819. return replace_macros(get_markup_template('design_tools.tpl'), array(
  1820. '$title' => t('Design Tools'),
  1821. '$who' => $who,
  1822. '$sys' => $sys,
  1823. '$blocks' => t('Blocks'),
  1824. '$menus' => t('Menus'),
  1825. '$layout' => t('Layouts'),
  1826. '$pages' => t('Pages')
  1827. ));
  1828. }
  1829. /* case insensitive in_array() */
  1830. function in_arrayi($needle, $haystack) {
  1831. return in_array(strtolower($needle), array_map('strtolower', $haystack));
  1832. }
  1833. function normalise_openid($s) {
  1834. return trim(str_replace(array('http://','https://'),array('',''),$s),'/');
  1835. }
  1836. // used in ajax endless scroll request to find out all the args that the master page was viewing.
  1837. // This was using $_REQUEST, but $_REQUEST also contains all your cookies. So we're restricting it
  1838. // to $_GET and $_POST.
  1839. function extra_query_args() {
  1840. $s = '';
  1841. if(count($_GET)) {
  1842. foreach($_GET as $k => $v) {
  1843. // these are request vars we don't want to duplicate
  1844. if(! in_array($k, array('q','f','zid','page','PHPSESSID'))) {
  1845. $s .= '&' . $k . '=' . urlencode($v);
  1846. }
  1847. }
  1848. }
  1849. if(count($_POST)) {
  1850. foreach($_POST as $k => $v) {
  1851. // these are request vars we don't want to duplicate
  1852. if(! in_array($k, array('q','f','zid','page','PHPSESSID'))) {
  1853. $s .= '&' . $k . '=' . urlencode($v);
  1854. }
  1855. }
  1856. }
  1857. return $s;
  1858. }
  1859. /**
  1860. * @brief This function removes the tag $tag from the text $body and replaces it
  1861. * with the appropiate link.
  1862. *
  1863. * @param App $a
  1864. * @param[in,out] string &$body the text to replace the tag in
  1865. * @param[in,out] string &$access_tag used to return tag ACL exclusions e.g. @!foo
  1866. * @param[in,out] string &$str_tags string to add the tag to
  1867. * @param int $profile_uid
  1868. * @param string $tag the tag to replace
  1869. * @param boolean $diaspora default false
  1870. * @return boolean true if replaced, false if not replaced
  1871. */
  1872. function handle_tag($a, &$body, &$access_tag, &$str_tags, $profile_uid, $tag, $diaspora = false) {
  1873. $replaced = false;
  1874. $r = null;
  1875. $match = array();
  1876. $termtype = ((strpos($tag,'#') === 0) ? TERM_HASHTAG : TERM_UNKNOWN);
  1877. $termtype = ((strpos($tag,'@') === 0) ? TERM_MENTION : $termtype);
  1878. $termtype = ((strpos($tag,'#^[') === 0) ? TERM_BOOKMARK : $termtype);
  1879. //is it a hash tag?
  1880. if(strpos($tag,'#') === 0) {
  1881. if(strpos($tag,'#^[') === 0) {
  1882. if(preg_match('/#\^\[(url|zrl)(.*?)\](.*?)\[\/(url|zrl)\]/',$tag,$match)) {
  1883. $basetag = $match[3];
  1884. $url = ((substr($match[2],0,1) === '=') ? substr($match[2],1) : $match[3]);
  1885. $replaced = true;
  1886. }
  1887. }
  1888. // if the tag is already replaced...
  1889. elseif((strpos($tag,'[zrl=')) || (strpos($tag,'[url='))) {
  1890. //...do nothing
  1891. return $replaced;
  1892. }
  1893. if($tag == '#getzot') {
  1894. $basetag = 'getzot';
  1895. $url = 'http://hubzilla.org';
  1896. $newtag = '#[zrl=' . $url . ']' . $basetag . '[/zrl]';
  1897. $body = str_replace($tag,$newtag,$body);
  1898. $replaced = true;
  1899. }
  1900. if(! $replaced) {
  1901. //base tag has the tags name only
  1902. if((substr($tag,0,7) === '#&quot;') && (substr($tag,-6,6) === '&quot;')) {
  1903. $basetag = substr($tag,7);
  1904. $basetag = substr($basetag,0,-6);
  1905. }
  1906. else
  1907. $basetag = str_replace('_',' ',substr($tag,1));
  1908. //create text for link
  1909. $url = z_root() . '/search?tag=' . rawurlencode($basetag);
  1910. $newtag = '#[zrl=' . z_root() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/zrl]';
  1911. //replace tag by the link. Make sure to not replace something in the middle of a word
  1912. // The '=' is needed to not replace color codes if the code is also used as a tag
  1913. // Much better would be to somehow completely avoiding things in e.g. [color]-tags.
  1914. // This would allow writing things like "my favourite tag=#foobar".
  1915. $body = preg_replace('/(?<![a-zA-Z0-9=])'.preg_quote($tag,'/').'/', $newtag, $body);
  1916. $replaced = true;
  1917. }
  1918. //is the link already in str_tags?
  1919. if(! stristr($str_tags,$newtag)) {
  1920. //append or set str_tags
  1921. if(strlen($str_tags))
  1922. $str_tags .= ',';
  1923. $str_tags .= $newtag;
  1924. }
  1925. return array('replaced' => $replaced, 'termtype' => $termtype, 'term' => $basetag, 'url' => $url, 'contact' => $r[0]);
  1926. }
  1927. //is it a person tag?
  1928. if(strpos($tag,'@') === 0) {
  1929. // The @! tag will alter permissions
  1930. $exclusive = ((strpos($tag,'!') === 1 && (! $diaspora)) ? true : false);
  1931. //is it already replaced?
  1932. if(strpos($tag,'[zrl='))
  1933. return $replaced;
  1934. //get the person's name
  1935. $name = substr($tag,(($exclusive) ? 2 : 1)); // The name or name fragment we are going to replace
  1936. $newname = $name; // a copy that we can mess with
  1937. $tagcid = 0;
  1938. $r = null;
  1939. // is it some generated name?
  1940. $forum = false;
  1941. $trailing_plus_name = false;
  1942. // @channel+ is a forum or network delivery tag
  1943. if(substr($newname,-1,1) === '+') {
  1944. $forum = true;
  1945. $newname = substr($newname,0,-1);
  1946. }
  1947. // Here we're looking for an address book entry as provided by the auto-completer
  1948. // of the form something+nnn where nnn is an abook_id or the first chars of xchan_hash
  1949. // If there's a +nnn in the string make sure there isn't a space preceding it
  1950. $t1 = strpos($newname,' ');
  1951. $t2 = strrpos($newname,'+');
  1952. if($t1 && $t2 && $t1 < $t2)
  1953. $t2 = 0;
  1954. if(($t2) && (! $diaspora)) {
  1955. //get the id
  1956. $tagcid = substr($newname,$t2 + 1);
  1957. if(strrpos($tagcid,' '))
  1958. $tagcid = substr($tagcid,0,strrpos($tagcid,' '));
  1959. if(strlen($tagcid) < 16)
  1960. $abook_id = intval($tagcid);
  1961. //remove the next word from tag's name
  1962. if(strpos($name,' ')) {
  1963. $name = substr($name,0,strpos($name,' '));
  1964. }
  1965. if($abook_id) { // if there was an id
  1966. // select channel with that id from the logged in user's address book
  1967. $r = q("SELECT * FROM abook left join xchan on abook_xchan = xchan_hash
  1968. WHERE abook_id = %d AND abook_channel = %d LIMIT 1",
  1969. intval($abook_id),
  1970. intval($profile_uid)
  1971. );
  1972. }
  1973. else {
  1974. $r = q("SELECT * FROM xchan
  1975. WHERE xchan_hash like '%s%%' LIMIT 1",
  1976. dbesc($tagcid)
  1977. );
  1978. }
  1979. }
  1980. if(! $r) {
  1981. // look for matching names in the address book
  1982. // Two ways to deal with spaces - double quote the name or use underscores
  1983. // we see this after input filtering so quotes have been html entity encoded
  1984. if((substr($name,0,6) === '&quot;') && (substr($name,-6,6) === '&quot;')) {
  1985. $newname = substr($name,6);
  1986. $newname = substr($newname,0,-6);
  1987. }
  1988. else
  1989. $newname = str_replace('_',' ',$name);
  1990. // do this bit over since we started over with $name
  1991. if(substr($newname,-1,1) === '+') {
  1992. $forum = true;
  1993. $newname = substr($newname,0,-1);
  1994. }
  1995. //select someone from this user's contacts by name
  1996. $r = q("SELECT * FROM abook left join xchan on abook_xchan = xchan_hash
  1997. WHERE xchan_name = '%s' AND abook_channel = %d LIMIT 1",
  1998. dbesc($newname),
  1999. intval($profile_uid)
  2000. );
  2001. if(! $r) {
  2002. //select someone by attag or nick and the name passed in
  2003. $r = q("SELECT * FROM abook left join xchan on abook_xchan = xchan_hash
  2004. WHERE xchan_addr like ('%s') AND abook_channel = %d LIMIT 1",
  2005. dbesc(((strpos($newname,'@')) ? $newname : $newname . '@%')),
  2006. intval($profile_uid)
  2007. );
  2008. }
  2009. if(! $r) {
  2010. // it's possible somebody has a name ending with '+', which we stripped off as a forum indicator
  2011. // This is very rare but we want to get it right.
  2012. $r = q("SELECT * FROM abook left join xchan on abook_xchan = xchan_hash
  2013. WHERE xchan_name = '%s' AND abook_channel = %d LIMIT 1",
  2014. dbesc($newname . '+'),
  2015. intval($profile_uid)
  2016. );
  2017. if($r)
  2018. $trailing_plus_name = true;
  2019. }
  2020. }
  2021. // $r is set if we found something
  2022. $channel = App::get_channel();
  2023. if($r) {
  2024. $profile = $r[0]['xchan_url'];
  2025. $newname = $r[0]['xchan_name'];
  2026. // add the channel's xchan_hash to $access_tag if exclusive
  2027. if($exclusive) {
  2028. $access_tag .= 'cid:' . $r[0]['xchan_hash'];
  2029. }
  2030. }
  2031. else {
  2032. // check for a group/collection exclusion tag
  2033. // note that we aren't setting $replaced even though we're replacing text.
  2034. // This tag isn't going to get a term attached to it. It's only used for
  2035. // access control. The link points to out own channel just so it doesn't look
  2036. // weird - as all the other tags are linked to something.
  2037. if(local_channel() && local_channel() == $profile_uid) {
  2038. require_once('include/group.php');
  2039. $grp = group_byname($profile_uid,$name);
  2040. if($grp) {
  2041. $g = q("select hash from groups where id = %d and visible = 1 limit 1",
  2042. intval($grp)
  2043. );
  2044. if($g && $exclusive) {
  2045. $access_tag .= 'gid:' . $g[0]['hash'];
  2046. }
  2047. $channel = App::get_channel();
  2048. if($channel) {
  2049. $newtag = '@' . (($exclusive) ? '!' : '') . '[zrl=' . z_root() . '/channel/' . $channel['channel_address'] . ']' . $newname . '[/zrl]';
  2050. $body = str_replace('@' . (($exclusive) ? '!' : '') . $name, $newtag, $body);
  2051. }
  2052. }
  2053. }
  2054. }
  2055. if(($exclusive) && (! $access_tag)) {
  2056. $access_tag .= 'cid:' . $channel['channel_hash'];
  2057. }
  2058. // if there is an url for this channel
  2059. if(isset($profile)) {
  2060. $replaced = true;
  2061. //create profile link
  2062. $profile = str_replace(',','%2c',$profile);
  2063. $url = $profile;
  2064. $newtag = '@' . (($exclusive) ? '!' : '') . '[zrl=' . $profile . ']' . $newname . (($forum && ! $trailing_plus_name) ? '+' : '') . '[/zrl]';
  2065. $body = str_replace('@' . (($exclusive) ? '!' : '') . $name, $newtag, $body);
  2066. //append tag to str_tags
  2067. if(! stristr($str_tags,$newtag)) {
  2068. if(strlen($str_tags))
  2069. $str_tags .= ',';
  2070. $str_tags .= $newtag;
  2071. }
  2072. }
  2073. }
  2074. return array('replaced' => $replaced, 'termtype' => $termtype, 'term' => $newname, 'url' => $url, 'contact' => $r[0]);
  2075. }
  2076. function linkify_tags($a, &$body, $uid, $diaspora = false) {
  2077. $str_tags = '';
  2078. $tagged = array();
  2079. $results = array();
  2080. $tags = get_tags($body);
  2081. if(count($tags)) {
  2082. foreach($tags as $tag) {
  2083. $access_tag = '';
  2084. // If we already tagged 'Robert Johnson', don't try and tag 'Robert'.
  2085. // Robert Johnson should be first in the $tags array
  2086. $fullnametagged = false;
  2087. for($x = 0; $x < count($tagged); $x ++) {
  2088. if(stristr($tagged[$x],$tag . ' ')) {
  2089. $fullnametagged = true;
  2090. break;
  2091. }
  2092. }
  2093. if($fullnametagged)
  2094. continue;
  2095. $success = handle_tag($a, $body, $access_tag, $str_tags, ($uid) ? $uid : App::$profile_uid , $tag, $diaspora);
  2096. $results[] = array('success' => $success, 'access_tag' => $access_tag);
  2097. if($success['replaced']) $tagged[] = $tag;
  2098. }
  2099. }
  2100. return $results;
  2101. }
  2102. /**
  2103. * @brief returns icon name for use with e.g. font-awesome based on mime-type.
  2104. *
  2105. * These are the the font-awesome names of version 3.2.1. The newer font-awesome
  2106. * 4 has different names.
  2107. *
  2108. * @param string $type mime type
  2109. * @return string
  2110. * @todo rename to get_icon_from_type()
  2111. */
  2112. function getIconFromType($type) {
  2113. $iconMap = array(
  2114. //Folder
  2115. t('Collection') => 'fa-folder',
  2116. 'multipart/mixed' => 'fa-folder', //dirs in attach use this mime type
  2117. //Common file
  2118. 'application/octet-stream' => 'fa-file-o',
  2119. //Text
  2120. 'text/plain' => 'fa-file-text-o',
  2121. 'application/msword' => 'fa-file-text-o',
  2122. 'application/pdf' => 'fa-file-text-o',
  2123. 'application/vnd.oasis.opendocument.text' => 'fa-file-text-o',
  2124. 'application/epub+zip' => 'fa-book',
  2125. //Spreadsheet
  2126. 'application/vnd.oasis.opendocument.spreadsheet' => 'fa-table',
  2127. 'application/vnd.ms-excel' => 'fa-table',
  2128. //Image
  2129. 'image/jpeg' => 'fa-picture-o',
  2130. 'image/png' => 'fa-picture-o',
  2131. 'image/gif' => 'fa-picture-o',
  2132. 'image/svg+xml' => 'fa-picture-o',
  2133. //Archive
  2134. 'application/zip' => 'fa-archive',
  2135. 'application/x-rar-compressed' => 'fa-archive',
  2136. //Audio
  2137. 'audio/mpeg' => 'fa-music',
  2138. 'audio/wav' => 'fa-music',
  2139. 'application/ogg' => 'fa-music',
  2140. 'audio/ogg' => 'fa-music',
  2141. 'audio/webm' => 'fa-music',
  2142. 'audio/mp4' => 'fa-music',
  2143. //Video
  2144. 'video/quicktime' => 'fa-film',
  2145. 'video/webm' => 'fa-film',
  2146. 'video/mp4' => 'fa-film'
  2147. );
  2148. $iconFromType = 'fa-file-o';
  2149. if (array_key_exists($type, $iconMap)) {
  2150. $iconFromType = $iconMap[$type];
  2151. }
  2152. return $iconFromType;
  2153. }
  2154. /**
  2155. * @brief Returns a human readable formatted string for filesizes.
  2156. *
  2157. * @param int $size filesize in bytes
  2158. * @return string human readable formatted filesize
  2159. * @todo rename to user_readable_size()
  2160. */
  2161. function userReadableSize($size) {
  2162. $ret = '';
  2163. if (is_numeric($size)) {
  2164. $incr = 0;
  2165. $k = 1024;
  2166. $unit = array('bytes', 'KB', 'MB', 'GB', 'TB', 'PB');
  2167. while (($size / $k) >= 1){
  2168. $incr++;
  2169. $size = round($size / $k, 2);
  2170. }
  2171. $ret = $size . ' ' . $unit[$incr];
  2172. }
  2173. return $ret;
  2174. }
  2175. function str_rot47($str) {
  2176. return strtr($str,
  2177. '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~',
  2178. 'PQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO');
  2179. }
  2180. function string_replace($old,$new,&$s) {
  2181. $x = str_replace($old,$new,$s);
  2182. $replaced = false;
  2183. if($x !== $s) {
  2184. $replaced = true;
  2185. }
  2186. $s = $x;
  2187. return $replaced;
  2188. }
  2189. function json_url_replace($old,$new,&$s) {
  2190. $old = str_replace('/','\\/',$old);
  2191. $new = str_replace('/','\\/',$new);
  2192. $x = str_replace($old,$new,$s);
  2193. $replaced = false;
  2194. if($x !== $s) {
  2195. $replaced = true;
  2196. }
  2197. $s = $x;
  2198. return $replaced;
  2199. }
  2200. function item_url_replace($channel,&$item,$old,$new,$oldnick = '') {
  2201. if($item['attach']) {
  2202. json_url_replace($old,$new,$item['attach']);
  2203. if($oldnick)
  2204. json_url_replace('/' . $oldnick . '/' ,'/' . $channel['channel_address'] . '/' ,$item['attach']);
  2205. }
  2206. if($item['object']) {
  2207. json_url_replace($old,$new,$item['object']);
  2208. if($oldnick)
  2209. json_url_replace('/' . $oldnick . '/' ,'/' . $channel['channel_address'] . '/' ,$item['object']);
  2210. }
  2211. if($item['target']) {
  2212. json_url_replace($old,$new,$item['target']);
  2213. if($oldnick)
  2214. json_url_replace('/' . $oldnick . '/' ,'/' . $channel['channel_address'] . '/' ,$item['target']);
  2215. }
  2216. if(string_replace($old,$new,$item['body'])) {
  2217. $item['sig'] = base64url_encode(rsa_sign($item['body'],$channel['channel_prvkey']));
  2218. $item['item_verified'] = 1;
  2219. }
  2220. $item['plink'] = str_replace($old,$new,$item['plink']);
  2221. if($oldnick)
  2222. $item['plink'] = str_replace('/' . $oldnick . '/' ,'/' . $channel['channel_address'] . '/' ,$item['plink']);
  2223. $item['llink'] = str_replace($old,$new,$item['llink']);
  2224. if($oldnick)
  2225. $item['llink'] = str_replace('/' . $oldnick . '/' ,'/' . $channel['channel_address'] . '/' ,$item['llink']);
  2226. }
  2227. /**
  2228. * @brief Used to wrap ACL elements in angle brackets for storage.
  2229. *
  2230. * @param[in,out] array &$item
  2231. */
  2232. function sanitise_acl(&$item) {
  2233. if (strlen($item))
  2234. $item = '<' . notags(trim($item)) . '>';
  2235. else
  2236. unset($item);
  2237. }
  2238. /**
  2239. * @brief Convert an ACL array to a storable string.
  2240. *
  2241. * @param array $p
  2242. * @return array
  2243. */
  2244. function perms2str($p) {
  2245. $ret = '';
  2246. if (is_array($p))
  2247. $tmp = $p;
  2248. else
  2249. $tmp = explode(',', $p);
  2250. if (is_array($tmp)) {
  2251. array_walk($tmp, 'sanitise_acl');
  2252. $ret = implode('', $tmp);
  2253. }
  2254. return $ret;
  2255. }
  2256. /**
  2257. * @brief Turn user/group ACLs stored as angle bracketed text into arrays.
  2258. *
  2259. * turn string array of angle-bracketed elements into string array
  2260. * e.g. "<123xyz><246qyo><sxo33e>" => array(123xyz,246qyo,sxo33e);
  2261. *
  2262. * @param string $s
  2263. * @return array
  2264. */
  2265. function expand_acl($s) {
  2266. $ret = array();
  2267. if(strlen($s)) {
  2268. $t = str_replace('<','',$s);
  2269. $a = explode('>',$t);
  2270. foreach($a as $aa) {
  2271. if($aa)
  2272. $ret[] = $aa;
  2273. }
  2274. }
  2275. return $ret;
  2276. }
  2277. // When editing a webpage - a dropdown is needed to select a page layout
  2278. // On submit, the pdl_select value (which is the mid of an item with item_type = ITEM_TYPE_PDL) is stored in
  2279. // the webpage's resource_id, with resource_type 'pdl'.
  2280. // Then when displaying a webpage, we can see if it has a pdl attached. If not we'll
  2281. // use the default site/page layout.
  2282. // If it has a pdl we'll load it as we know the mid and pass the body through comanche_parser() which will generate the
  2283. // page layout from the given description
  2284. function pdl_selector($uid, $current="") {
  2285. $o = '';
  2286. $sql_extra = item_permissions_sql($uid);
  2287. $r = q("select item_id.*, mid from item_id left join item on iid = item.id where item_id.uid = %d and item_id.uid = item.uid and service = 'PDL' $sql_extra order by sid asc",
  2288. intval($uid)
  2289. );
  2290. $arr = array('channel_id' => $uid, 'current' => $current, 'entries' => $r);
  2291. call_hooks('pdl_selector',$arr);
  2292. $entries = $arr['entries'];
  2293. $current = $arr['current'];
  2294. $o .= '<select name="pdl_select" id="pdl_select" size="1">';
  2295. $entries[] = array('title' => t('Default'), 'mid' => '');
  2296. foreach($entries as $selection) {
  2297. $selected = (($selection == $current) ? ' selected="selected" ' : '');
  2298. $o .= "<option value=\"{$selection['mid']}\" $selected >{$selection['sid']}</option>";
  2299. }
  2300. $o .= '</select>';
  2301. return $o;
  2302. }
  2303. /*
  2304. * array flatten_array_recursive(array);
  2305. * returns a one-dimensional array from a multi-dimensional array
  2306. * empty values are discarded
  2307. * example: print_r(flatten_array_recursive(array('foo','bar',array('baz','blip',array('zob','glob')),'','grip')));
  2308. *
  2309. * Array ( [0] => foo [1] => bar [2] => baz [3] => blip [4] => zob [5] => glob [6] => grip )
  2310. *
  2311. */
  2312. function flatten_array_recursive($arr) {
  2313. $ret = array();
  2314. if(! $arr)
  2315. return $ret;
  2316. foreach($arr as $a) {
  2317. if(is_array($a)) {
  2318. $tmp = flatten_array_recursive($a);
  2319. if($tmp) {
  2320. $ret = array_merge($ret,$tmp);
  2321. }
  2322. }
  2323. elseif($a) {
  2324. $ret[] = $a;
  2325. }
  2326. }
  2327. return($ret);
  2328. }