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

1947 lines
53 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. <?php
  2. set_time_limit(0);
  3. define ( 'BUILD_ID', 1023 );
  4. define ( 'DFRN_PROTOCOL_VERSION', '2.0' );
  5. define ( 'EOL', "<br />\r\n" );
  6. define ( 'ATOM_TIME', 'Y-m-d\TH:i:s\Z' );
  7. define ( 'DOWN_ARROW', '&#x21e9;' );
  8. /**
  9. * log levels
  10. */
  11. define ( 'LOGGER_NORMAL', 0 );
  12. define ( 'LOGGER_TRACE', 1 );
  13. define ( 'LOGGER_DEBUG', 2 );
  14. define ( 'LOGGER_DATA', 3 );
  15. define ( 'LOGGER_ALL', 4 );
  16. /**
  17. * registration policies
  18. */
  19. define ( 'REGISTER_CLOSED', 0 );
  20. define ( 'REGISTER_APPROVE', 1 );
  21. define ( 'REGISTER_OPEN', 2 );
  22. /**
  23. * relationship types
  24. */
  25. define ( 'REL_VIP', 1);
  26. define ( 'REL_FAN', 2);
  27. define ( 'REL_BUD', 3);
  28. /**
  29. *
  30. * page/profile types
  31. *
  32. * PAGE_NORMAL is a typical personal profile account
  33. * PAGE_SOAPBOX automatically approves all friend requests as REL_FAN, (readonly)
  34. * PAGE_COMMUNITY automatically approves all friend requests as REL_FAN, but with
  35. * write access to wall and comments (no email and not included in page owner's ACL lists)
  36. * PAGE_FREELOVE automatically approves all friend requests as full friends (REL_BUD).
  37. *
  38. */
  39. define ( 'PAGE_NORMAL', 0 );
  40. define ( 'PAGE_SOAPBOX', 1 );
  41. define ( 'PAGE_COMMUNITY', 2 );
  42. define ( 'PAGE_FREELOVE', 3 );
  43. /**
  44. * Maximum number of "people who like (or don't like) this" that we will list by name
  45. */
  46. define ( 'MAX_LIKERS', 75);
  47. /**
  48. * email notification options
  49. */
  50. define ( 'NOTIFY_INTRO', 0x0001 );
  51. define ( 'NOTIFY_CONFIRM', 0x0002 );
  52. define ( 'NOTIFY_WALL', 0x0004 );
  53. define ( 'NOTIFY_COMMENT', 0x0008 );
  54. define ( 'NOTIFY_MAIL', 0x0010 );
  55. /**
  56. * various namespaces we may need to parse
  57. */
  58. define ( 'NAMESPACE_DFRN' , 'http://purl.org/macgirvin/dfrn/1.0' );
  59. define ( 'NAMESPACE_THREAD' , 'http://purl.org/syndication/thread/1.0' );
  60. define ( 'NAMESPACE_TOMB' , 'http://purl.org/atompub/tombstones/1.0' );
  61. define ( 'NAMESPACE_ACTIVITY', 'http://activitystrea.ms/spec/1.0/' );
  62. define ( 'NAMESPACE_ACTIVITY_SCHEMA', 'http://activitystrea.ms/schema/1.0/' );
  63. define ( 'NAMESPACE_MEDIA', 'http://purl.org/syndication/atommedia' );
  64. define ( 'NAMESPACE_SALMON_ME', 'http://salmon-protocol.org/ns/magic-env' );
  65. define ( 'NAMESPACE_OSTATUSSUB', 'http://ostatus.org/schema/1.0/subscribe' );
  66. define ( 'NAMESPACE_GEORSS', 'http://www.georss.org/georss' );
  67. define ( 'NAMESPACE_POCO', 'http://portablecontacts.net/spec/1.0' );
  68. define ( 'NAMESPACE_FEED', 'http://schemas.google.com/g/2010#updates-from' );
  69. /**
  70. * activity stream defines
  71. */
  72. define ( 'ACTIVITY_LIKE', NAMESPACE_ACTIVITY_SCHEMA . 'like' );
  73. define ( 'ACTIVITY_DISLIKE', NAMESPACE_DFRN . '/dislike' );
  74. define ( 'ACTIVITY_OBJ_HEART', NAMESPACE_DFRN . '/heart' );
  75. define ( 'ACTIVITY_FRIEND', NAMESPACE_ACTIVITY_SCHEMA . 'make-friend' );
  76. define ( 'ACTIVITY_FOLLOW', NAMESPACE_ACTIVITY_SCHEMA . 'follow' );
  77. define ( 'ACTIVITY_UNFOLLOW', NAMESPACE_ACTIVITY_SCHEMA . 'unfollow' );
  78. define ( 'ACTIVITY_POST', NAMESPACE_ACTIVITY_SCHEMA . 'post' );
  79. define ( 'ACTIVITY_UPDATE', NAMESPACE_ACTIVITY_SCHEMA . 'update' );
  80. define ( 'ACTIVITY_TAG', NAMESPACE_ACTIVITY_SCHEMA . 'tag' );
  81. define ( 'ACTIVITY_OBJ_COMMENT', NAMESPACE_ACTIVITY_SCHEMA . 'comment' );
  82. define ( 'ACTIVITY_OBJ_NOTE', NAMESPACE_ACTIVITY_SCHEMA . 'note' );
  83. define ( 'ACTIVITY_OBJ_PERSON', NAMESPACE_ACTIVITY_SCHEMA . 'person' );
  84. define ( 'ACTIVITY_OBJ_PHOTO', NAMESPACE_ACTIVITY_SCHEMA . 'photo' );
  85. define ( 'ACTIVITY_OBJ_P_PHOTO', NAMESPACE_ACTIVITY_SCHEMA . 'profile-photo' );
  86. define ( 'ACTIVITY_OBJ_ALBUM', NAMESPACE_ACTIVITY_SCHEMA . 'photo-album' );
  87. /**
  88. * item weight for query ordering
  89. */
  90. define ( 'GRAVITY_PARENT', 0);
  91. define ( 'GRAVITY_LIKE', 3);
  92. define ( 'GRAVITY_COMMENT', 6);
  93. /**
  94. *
  95. * Reverse the effect of magic_quotes_gpc if it is enabled.
  96. * Please disable magic_quotes_gpc so we don't have to do this.
  97. * See http://php.net/manual/en/security.magicquotes.disabling.php
  98. *
  99. */
  100. if (get_magic_quotes_gpc()) {
  101. $process = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
  102. while (list($key, $val) = each($process)) {
  103. foreach ($val as $k => $v) {
  104. unset($process[$key][$k]);
  105. if (is_array($v)) {
  106. $process[$key][stripslashes($k)] = $v;
  107. $process[] = &$process[$key][stripslashes($k)];
  108. } else {
  109. $process[$key][stripslashes($k)] = stripslashes($v);
  110. }
  111. }
  112. }
  113. unset($process);
  114. }
  115. /**
  116. *
  117. * class: App
  118. *
  119. * Our main application structure for the life of this page
  120. * Primarily deals with the URL that got us here
  121. * and tries to make some sense of it, and
  122. * stores our page contents and config storage
  123. * and anything else that might need to be passed around
  124. * before we spit the page out.
  125. *
  126. */
  127. if(! class_exists('App')) {
  128. class App {
  129. public $module_loaded = false;
  130. public $query_string;
  131. public $config;
  132. public $page;
  133. public $profile;
  134. public $user;
  135. public $cid;
  136. public $contact;
  137. public $content;
  138. public $data;
  139. public $error = false;
  140. public $cmd;
  141. public $argv;
  142. public $argc;
  143. public $module;
  144. public $pager;
  145. public $strings;
  146. public $path;
  147. public $interactive = true;
  148. private $scheme;
  149. private $hostname;
  150. private $baseurl;
  151. private $db;
  152. private $curl_code;
  153. private $curl_headers;
  154. function __construct() {
  155. $this->config = array();
  156. $this->page = array();
  157. $this->pager= array();
  158. $this->query_string = '';
  159. $this->scheme = ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'])) ? 'https' : 'http' );
  160. if(x($_SERVER,'SERVER_NAME'))
  161. $this->hostname = $_SERVER['SERVER_NAME'];
  162. set_include_path("include/$this->hostname" . PATH_SEPARATOR . 'include' . PATH_SEPARATOR . '.' );
  163. if((x($_SERVER,'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'],0,2) === "q=")
  164. $this->query_string = substr($_SERVER['QUERY_STRING'],2);
  165. if(x($_GET,'q'))
  166. $this->cmd = trim($_GET['q'],'/');
  167. /**
  168. * Figure out if we are running at the top of a domain
  169. * or in a sub-directory and adjust accordingly
  170. */
  171. $path = trim(dirname($_SERVER['SCRIPT_NAME']),'/');
  172. if(isset($path) && strlen($path) && ($path != $this->path))
  173. $this->path = $path;
  174. /**
  175. *
  176. * Break the URL path into C style argc/argv style arguments for our
  177. * modules. Given "http://example.com/module/arg1/arg2", $this->argc
  178. * will be 3 (integer) and $this->argv will contain:
  179. * [0] => 'module'
  180. * [1] => 'arg1'
  181. * [2] => 'arg2'
  182. *
  183. *
  184. * There will always be one argument. If provided a naked domain
  185. * URL, $this->argv[0] is set to "home".
  186. *
  187. */
  188. $this->argv = explode('/',$this->cmd);
  189. $this->argc = count($this->argv);
  190. if((array_key_exists('0',$this->argv)) && strlen($this->argv[0])) {
  191. $this->module = $this->argv[0];
  192. }
  193. else {
  194. $this->module = 'home';
  195. }
  196. /**
  197. * Special handling for the webfinger/lrdd host XRD file
  198. * Just spit out the contents and exit.
  199. */
  200. if($this->cmd === '.well-known/host-meta')
  201. require_once('include/hostxrd.php');
  202. /**
  203. * See if there is any page number information, and initialise
  204. * pagination
  205. */
  206. $this->pager['page'] = ((x($_GET,'page')) ? $_GET['page'] : 1);
  207. $this->pager['itemspage'] = 50;
  208. $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
  209. $this->pager['total'] = 0;
  210. }
  211. function get_baseurl($ssl = false) {
  212. if(strlen($this->baseurl))
  213. return $this->baseurl;
  214. $this->baseurl = (($ssl) ? 'https' : $this->scheme) . "://" . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );
  215. return $this->baseurl;
  216. }
  217. function set_baseurl($url) {
  218. $this->baseurl = $url;
  219. $this->hostname = basename($url);
  220. }
  221. function get_hostname() {
  222. return $this->hostname;
  223. }
  224. function set_hostname($h) {
  225. $this->hostname = $h;
  226. }
  227. function set_path($p) {
  228. $this->path = trim(trim($p),'/');
  229. }
  230. function get_path() {
  231. return $this->path;
  232. }
  233. function set_pager_total($n) {
  234. $this->pager['total'] = intval($n);
  235. }
  236. function set_pager_itemspage($n) {
  237. $this->pager['itemspage'] = intval($n);
  238. $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
  239. }
  240. function init_pagehead() {
  241. $tpl = load_view_file("view/head.tpl");
  242. $this->page['htmlhead'] = replace_macros($tpl,array(
  243. '$baseurl' => $this->get_baseurl() . '/'
  244. ));
  245. }
  246. function set_curl_code($code) {
  247. $this->curl_code = $code;
  248. }
  249. function get_curl_code() {
  250. return $this->curl_code;
  251. }
  252. function set_curl_headers($headers) {
  253. $this->curl_headers = $headers;
  254. }
  255. function get_curl_headers() {
  256. return $this->curl_headers;
  257. }
  258. }}
  259. // retrieve the App structure
  260. // useful in functions which require it but don't get it passed to them
  261. if(! function_exists('get_app')) {
  262. function get_app() {
  263. global $a;
  264. return $a;
  265. }};
  266. // Multi-purpose function to check variable state.
  267. // Usage: x($var) or $x($array,'key')
  268. // returns false if variable/key is not set
  269. // if variable is set, returns 1 if has 'non-zero' value, otherwise returns 0.
  270. // e.g. x('') or x(0) returns 0;
  271. if(! function_exists('x')) {
  272. function x($s,$k = NULL) {
  273. if($k != NULL) {
  274. if((is_array($s)) && (array_key_exists($k,$s))) {
  275. if($s[$k])
  276. return (int) 1;
  277. return (int) 0;
  278. }
  279. return false;
  280. }
  281. else {
  282. if(isset($s)) {
  283. if($s) {
  284. return (int) 1;
  285. }
  286. return (int) 0;
  287. }
  288. return false;
  289. }
  290. }}
  291. // called from db initialisation if db is dead.
  292. if(! function_exists('system_unavailable')) {
  293. function system_unavailable() {
  294. include('system_unavailable.php');
  295. killme();
  296. }}
  297. // Primarily involved with database upgrade, but also sets the
  298. // base url for use in cmdline programs which don't have
  299. // $_SERVER variables.
  300. if(! function_exists('check_config')) {
  301. function check_config(&$a) {
  302. load_config('system');
  303. $build = get_config('system','build');
  304. if(! x($build))
  305. $build = set_config('system','build',BUILD_ID);
  306. $url = get_config('system','url');
  307. if(! x($url))
  308. $url = set_config('system','url',$a->get_baseurl());
  309. if($build != BUILD_ID) {
  310. $stored = intval($build);
  311. $current = intval(BUILD_ID);
  312. if(($stored < $current) && file_exists('update.php')) {
  313. // We're reporting a different version than what is currently installed.
  314. // Run any existing update scripts to bring the database up to current.
  315. require_once('update.php');
  316. for($x = $stored; $x < $current; $x ++) {
  317. if(function_exists('update_' . $x)) {
  318. $func = 'update_' . $x;
  319. $func($a);
  320. }
  321. }
  322. set_config('system','build', BUILD_ID);
  323. }
  324. }
  325. return;
  326. }}
  327. // This is our template processor.
  328. // $s is the string requiring macro substitution.
  329. // $r is an array of key value pairs (search => replace)
  330. // returns substituted string.
  331. // WARNING: this is pretty basic, and doesn't properly handle search strings that are substrings of each other.
  332. // For instance if 'test' => "foo" and 'testing' => "bar", testing could become either bar or fooing,
  333. // depending on the order in which they were declared in the array.
  334. if(! function_exists('replace_macros')) {
  335. function replace_macros($s,$r) {
  336. $search = array();
  337. $replace = array();
  338. if(is_array($r) && count($r)) {
  339. foreach ($r as $k => $v ) {
  340. $search[] = $k;
  341. $replace[] = $v;
  342. }
  343. }
  344. return str_replace($search,$replace,$s);
  345. }}
  346. // load string translation table for alternate language
  347. if(! function_exists('load_translation_table')) {
  348. function load_translation_table($lang) {
  349. global $a;
  350. if(file_exists("view/$lang/strings.php"))
  351. include("view/$lang/strings.php");
  352. }}
  353. // translate string if translation exists
  354. if(! function_exists('t')) {
  355. function t($s) {
  356. $a = get_app();
  357. if(x($a->strings,$s))
  358. return $a->strings[$s];
  359. return $s;
  360. }}
  361. // curl wrapper. If binary flag is true, return binary
  362. // results.
  363. if(! function_exists('fetch_url')) {
  364. function fetch_url($url,$binary = false, &$redirects = 0) {
  365. $a = get_app();
  366. $ch = curl_init($url);
  367. if(($redirects > 8) || (! $ch))
  368. return false;
  369. curl_setopt($ch, CURLOPT_HEADER, true);
  370. curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
  371. $curl_time = intval(get_config('system','curl_timeout'));
  372. curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
  373. // by default we will allow self-signed certs
  374. // but you can override this
  375. $check_cert = get_config('system','verifyssl');
  376. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
  377. $prx = get_config('system','proxy');
  378. if(strlen($prx)) {
  379. curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
  380. curl_setopt($ch, CURLOPT_PROXY, $prx);
  381. $prxusr = get_config('system','proxyuser');
  382. if(strlen($prxusr))
  383. curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
  384. }
  385. if($binary)
  386. curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
  387. $a->set_curl_code(0);
  388. // don't let curl abort the entire application
  389. // if it throws any errors.
  390. $s = @curl_exec($ch);
  391. $http_code = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
  392. $header = substr($s,0,strpos($s,"\r\n\r\n"));
  393. if(stristr($header,'100') && (strlen($header) < 30)) {
  394. // 100 Continue has two headers, get the real one
  395. $s = substr($s,strlen($header)+4);
  396. $header = substr($s,0,strpos($s,"\r\n\r\n"));
  397. }
  398. if($http_code == 301 || $http_code == 302 || $http_code == 303) {
  399. $matches = array();
  400. preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
  401. $url = trim(array_pop($matches));
  402. $url_parsed = parse_url($url);
  403. if (isset($url_parsed)) {
  404. $redirects++;
  405. return fetch_url($url,$binary,$redirects);
  406. }
  407. }
  408. $a->set_curl_code($http_code);
  409. $body = substr($s,strlen($header)+4);
  410. $a->set_curl_headers($header);
  411. curl_close($ch);
  412. return($body);
  413. }}
  414. // post request to $url. $params is an array of post variables.
  415. if(! function_exists('post_url')) {
  416. function post_url($url,$params, $headers = null, &$redirects = 0) {
  417. $a = get_app();
  418. $ch = curl_init($url);
  419. if(($redirects > 8) || (! $ch))
  420. return false;
  421. curl_setopt($ch, CURLOPT_HEADER, true);
  422. curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
  423. curl_setopt($ch, CURLOPT_POST,1);
  424. curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
  425. $curl_time = intval(get_config('system','curl_timeout'));
  426. curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
  427. if(is_array($headers))
  428. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  429. $check_cert = get_config('system','verifyssl');
  430. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
  431. $prx = get_config('system','proxy');
  432. if(strlen($prx)) {
  433. curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
  434. curl_setopt($ch, CURLOPT_PROXY, $prx);
  435. $prxusr = get_config('system','proxyuser');
  436. if(strlen($prxusr))
  437. curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
  438. }
  439. $a->set_curl_code(0);
  440. // don't let curl abort the entire application
  441. // if it throws any errors.
  442. $s = @curl_exec($ch);
  443. $http_code = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
  444. $header = substr($s,0,strpos($s,"\r\n\r\n"));
  445. if(stristr($header,'100') && (strlen($header) < 30)) {
  446. // 100 Continue has two headers, get the real one
  447. $s = substr($s,strlen($header)+4);
  448. $header = substr($s,0,strpos($s,"\r\n\r\n"));
  449. }
  450. if($http_code == 301 || $http_code == 302 || $http_code == 303) {
  451. $matches = array();
  452. preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
  453. $url = trim(array_pop($matches));
  454. $url_parsed = parse_url($url);
  455. if (isset($url_parsed)) {
  456. $redirects++;
  457. return post_url($url,$binary,$headers,$redirects);
  458. }
  459. }
  460. $a->set_curl_code($http_code);
  461. $body = substr($s,strlen($header)+4);
  462. $a->set_curl_headers($header);
  463. curl_close($ch);
  464. return($body);
  465. }}
  466. // random hash, 64 chars
  467. if(! function_exists('random_string')) {
  468. function random_string() {
  469. return(hash('sha256',uniqid(rand(),true)));
  470. }}
  471. /**
  472. * This is our primary input filter.
  473. *
  474. * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
  475. * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
  476. * after cleansing, and angle chars with the high bit set could get through as markup.
  477. *
  478. * This is now disabled because it was interfering with some legitimate unicode sequences
  479. * and hopefully there aren't a lot of those browsers left.
  480. *
  481. * Use this on any text input where angle chars are not valid or permitted
  482. * They will be replaced with safer brackets. This may be filtered further
  483. * if these are not allowed either.
  484. *
  485. */
  486. if(! function_exists('notags')) {
  487. function notags($string) {
  488. return(str_replace(array("<",">"), array('[',']'), $string));
  489. // High-bit filter no longer used
  490. // return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
  491. }}
  492. // use this on "body" or "content" input where angle chars shouldn't be removed,
  493. // and allow them to be safely displayed.
  494. if(! function_exists('escape_tags')) {
  495. function escape_tags($string) {
  496. return(htmlspecialchars($string));
  497. }}
  498. // wrapper for adding a login box. If $register == true provide a registration
  499. // link. This will most always depend on the value of $a->config['register_policy'].
  500. // returns the complete html for inserting into the page
  501. if(! function_exists('login')) {
  502. function login($register = false) {
  503. $o = "";
  504. $register_html = (($register) ? load_view_file("view/register-link.tpl") : "");
  505. $noid = get_config('system','no_openid');
  506. if($noid) {
  507. $classname = 'no-openid';
  508. $namelabel = t('Nickname or Email address: ');
  509. $passlabel = t('Password: ');
  510. $login = t('Login');
  511. }
  512. else {
  513. $classname = 'openid';
  514. $namelabel = t('Nickname/Email/OpenID: ');
  515. $passlabel = t("Password \x28if not OpenID\x29: ");
  516. $login = t('Login');
  517. }
  518. $lostpass = t('Forgot your password?');
  519. $lostlink = t('Password Reset');
  520. if(x($_SESSION,'authenticated')) {
  521. $tpl = load_view_file("view/logout.tpl");
  522. }
  523. else {
  524. $tpl = load_view_file("view/login.tpl");
  525. }
  526. $o = replace_macros($tpl,array(
  527. '$register_html' => $register_html,
  528. '$classname' => $classname,
  529. '$namelabel' => $namelabel,
  530. '$passlabel' => $passlabel,
  531. '$login' => $login,
  532. '$lostpass' => $lostpass,
  533. '$lostlink' => $lostlink
  534. ));
  535. return $o;
  536. }}
  537. // generate a string that's random, but usually pronounceable.
  538. // used to generate initial passwords
  539. if(! function_exists('autoname')) {
  540. function autoname($len) {
  541. $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u');
  542. if(mt_rand(0,5) == 4)
  543. $vowels[] = 'y';
  544. $cons = array(
  545. 'b','bl','br',
  546. 'c','ch','cl','cr',
  547. 'd','dr',
  548. 'f','fl','fr',
  549. 'g','gh','gl','gr',
  550. 'h',
  551. 'j',
  552. 'k','kh','kl','kr',
  553. 'l',
  554. 'm',
  555. 'n',
  556. 'p','ph','pl','pr',
  557. 'qu',
  558. 'r','rh',
  559. 's','sc','sh','sm','sp','st',
  560. 't','th','tr',
  561. 'v',
  562. 'w','wh',
  563. 'x',
  564. 'z','zh'
  565. );
  566. $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
  567. 'nd','ng','nk','nt','rn','rp','rt');
  568. $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
  569. 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
  570. $start = mt_rand(0,2);
  571. if($start == 0)
  572. $table = $vowels;
  573. else
  574. $table = $cons;
  575. $word = '';
  576. for ($x = 0; $x < $len; $x ++) {
  577. $r = mt_rand(0,count($table) - 1);
  578. $word .= $table[$r];
  579. if($table == $vowels)
  580. $table = array_merge($cons,$midcons);
  581. else
  582. $table = $vowels;
  583. }
  584. $word = substr($word,0,$len);
  585. foreach($noend as $noe) {
  586. if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
  587. $word = substr($word,0,-1);
  588. break;
  589. }
  590. }
  591. if(substr($word,-1) == 'q')
  592. $word = substr($word,0,-1);
  593. return $word;
  594. }}
  595. // Used to end the current process, after saving session state.
  596. if(! function_exists('killme')) {
  597. function killme() {
  598. session_write_close();
  599. exit;
  600. }}
  601. // redirect to another URL and terminate this process.
  602. if(! function_exists('goaway')) {
  603. function goaway($s) {
  604. header("Location: $s");
  605. killme();
  606. }}
  607. // Generic XML return
  608. // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable
  609. // of $st and an optional text <message> of $message and terminates the current process.
  610. if(! function_exists('xml_status')) {
  611. function xml_status($st, $message = '') {
  612. $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
  613. if($st)
  614. logger('xml_status returning non_zero: ' . $st . " message=" . $message);
  615. header( "Content-type: text/xml" );
  616. echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
  617. echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
  618. killme();
  619. }}
  620. // Returns the uid of locally logged in user or false.
  621. if(! function_exists('local_user')) {
  622. function local_user() {
  623. if((x($_SESSION,'authenticated')) && (x($_SESSION,'uid')))
  624. return intval($_SESSION['uid']);
  625. return false;
  626. }}
  627. // Returns contact id of authenticated site visitor or false
  628. if(! function_exists('remote_user')) {
  629. function remote_user() {
  630. if((x($_SESSION,'authenticated')) && (x($_SESSION,'visitor_id')))
  631. return intval($_SESSION['visitor_id']);
  632. return false;
  633. }}
  634. // contents of $s are displayed prominently on the page the next time
  635. // a page is loaded. Usually used for errors or alerts.
  636. if(! function_exists('notice')) {
  637. function notice($s) {
  638. $a = get_app();
  639. if($a->interactive)
  640. $_SESSION['sysmsg'] .= $s;
  641. }}
  642. // wrapper around config to limit the text length of an incoming message
  643. if(! function_exists('get_max_import_size')) {
  644. function get_max_import_size() {
  645. global $a;
  646. return ((x($a->config,'max_import_size')) ? $a->config['max_import_size'] : 0 );
  647. }}
  648. // escape text ($str) for XML transport
  649. // returns escaped text.
  650. if(! function_exists('xmlify')) {
  651. function xmlify($str) {
  652. $buffer = '';
  653. for($x = 0; $x < strlen($str); $x ++) {
  654. $char = $str[$x];
  655. switch( $char ) {
  656. case "\r" :
  657. break;
  658. case "&" :
  659. $buffer .= '&amp;';
  660. break;
  661. case "'" :
  662. $buffer .= '&apos;';
  663. break;
  664. case "\"" :
  665. $buffer .= '&quot;';
  666. break;
  667. case '<' :
  668. $buffer .= '&lt;';
  669. break;
  670. case '>' :
  671. $buffer .= '&gt;';
  672. break;
  673. case "\n" :
  674. $buffer .= "\n";
  675. break;
  676. default :
  677. $buffer .= $char;
  678. break;
  679. }
  680. }
  681. $buffer = trim($buffer);
  682. return($buffer);
  683. }}
  684. // undo an xmlify
  685. // pass xml escaped text ($s), returns unescaped text
  686. if(! function_exists('unxmlify')) {
  687. function unxmlify($s) {
  688. $ret = str_replace('&amp;','&', $s);
  689. $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
  690. return $ret;
  691. }}
  692. // convenience wrapper, reverse the operation "bin2hex"
  693. if(! function_exists('hex2bin')) {
  694. function hex2bin($s) {
  695. return(pack("H*",$s));
  696. }}
  697. // Automatic pagination.
  698. // To use, get the count of total items.
  699. // Then call $a->set_pager_total($number_items);
  700. // Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
  701. // Then call paginate($a) after the end of the display loop to insert the pager block on the page
  702. // (assuming there are enough items to paginate).
  703. // When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
  704. // will limit the results to the correct items for the current page.
  705. // The actual page handling is then accomplished at the application layer.
  706. if(! function_exists('paginate')) {
  707. function paginate(&$a) {
  708. $o = '';
  709. $stripped = preg_replace('/(&page=[0-9]*)/','',$a->query_string);
  710. $stripped = str_replace('q=','',$stripped);
  711. $stripped = trim($stripped,'/');
  712. $url = $a->get_baseurl() . '/' . $stripped;
  713. if($a->pager['total'] > $a->pager['itemspage']) {
  714. $o .= '<div class="pager">';
  715. if($a->pager['page'] != 1)
  716. $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
  717. $o .= "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
  718. $numpages = $a->pager['total'] / $a->pager['itemspage'];
  719. $numstart = 1;
  720. $numstop = $numpages;
  721. if($numpages > 14) {
  722. $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
  723. $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
  724. }
  725. for($i = $numstart; $i <= $numstop; $i++){
  726. if($i == $a->pager['page'])
  727. $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
  728. else
  729. $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
  730. $o .= '</span> ';
  731. }
  732. if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
  733. if($i == $a->pager['page'])
  734. $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
  735. else
  736. $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
  737. $o .= '</span> ';
  738. }
  739. $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
  740. $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
  741. if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
  742. $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
  743. $o .= '</div>'."\r\n";
  744. }
  745. return $o;
  746. }}
  747. // Turn user/group ACLs stored as angle bracketed text into arrays
  748. if(! function_exists('expand_acl')) {
  749. function expand_acl($s) {
  750. // turn string array of angle-bracketed elements into numeric array
  751. // e.g. "<1><2><3>" => array(1,2,3);
  752. $ret = array();
  753. if(strlen($s)) {
  754. $t = str_replace('<','',$s);
  755. $a = explode('>',$t);
  756. foreach($a as $aa) {
  757. if(intval($aa))
  758. $ret[] = intval($aa);
  759. }
  760. }
  761. return $ret;
  762. }}
  763. // Used to wrap ACL elements in angle brackets for storage
  764. if(! function_exists('sanitise_acl')) {
  765. function sanitise_acl(&$item) {
  766. if(intval($item))
  767. $item = '<' . intval(notags(trim($item))) . '>';
  768. else
  769. unset($item);
  770. }}
  771. // retrieve a "family" of config variables from database to cached storage
  772. if(! function_exists('load_config')) {
  773. function load_config($family) {
  774. global $a;
  775. $r = q("SELECT * FROM `config` WHERE `cat` = '%s'",
  776. dbesc($family)
  777. );
  778. if(count($r)) {
  779. foreach($r as $rr) {
  780. $k = $rr['k'];
  781. $a->config[$family][$k] = $rr['v'];
  782. }
  783. }
  784. }}
  785. // get a particular config variable given the family name
  786. // and key. Returns false if not set.
  787. // $instore is only used by the set_config function
  788. // to determine if the key already exists in the DB
  789. // If a key is found in the DB but doesn't exist in
  790. // local config cache, pull it into the cache so we don't have
  791. // to hit the DB again for this item.
  792. if(! function_exists('get_config')) {
  793. function get_config($family, $key, $instore = false) {
  794. global $a;
  795. if(! $instore) {
  796. if(isset($a->config[$family][$key])) {
  797. if($a->config[$family][$key] === '!<unset>!') {
  798. return false;
  799. }
  800. return $a->config[$family][$key];
  801. }
  802. }
  803. $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
  804. dbesc($family),
  805. dbesc($key)
  806. );
  807. if(count($ret)) {
  808. $a->config[$family][$key] = $ret[0]['v'];
  809. return $ret[0]['v'];
  810. }
  811. else {
  812. $a->config[$family][$key] = '!<unset>!';
  813. }
  814. return false;
  815. }}
  816. // Store a config value ($value) in the category ($family)
  817. // under the key ($key)
  818. // Return the value, or false if the database update failed
  819. if(! function_exists('set_config')) {
  820. function set_config($family,$key,$value) {
  821. global $a;
  822. $a->config[$family][$key] = $value;
  823. if(get_config($family,$key,true) === false) {
  824. $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
  825. dbesc($family),
  826. dbesc($key),
  827. dbesc($value)
  828. );
  829. if($ret)
  830. return $value;
  831. return $ret;
  832. }
  833. $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
  834. dbesc($value),
  835. dbesc($family),
  836. dbesc($key)
  837. );
  838. if($ret)
  839. return $value;
  840. return $ret;
  841. }}
  842. if(! function_exists('get_pconfig')) {
  843. function get_pconfig($uid,$family, $key, $instore = false) {
  844. global $a;
  845. if(! $instore) {
  846. if(isset($a->config[$uid][$family][$key])) {
  847. if($a->config[$uid][$family][$key] === '!<unset>!') {
  848. return false;
  849. }
  850. return $a->config[$uid][$family][$key];
  851. }
  852. }
  853. $ret = q("SELECT `v` FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
  854. intval($uid),
  855. dbesc($family),
  856. dbesc($key)
  857. );
  858. if(count($ret)) {
  859. $a->config[$uid][$family][$key] = $ret[0]['v'];
  860. return $ret[0]['v'];
  861. }
  862. else {
  863. $a->config[$uid][$family][$key] = '!<unset>!';
  864. }
  865. return false;
  866. }}
  867. if(! function_exists('del_config')) {
  868. function del_config($family,$key) {
  869. global $a;
  870. if(x($a->config[$family],$key))
  871. unset($a->config[$family][$key]);
  872. $ret = q("DELETE FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
  873. dbesc($cat),
  874. dbesc($key)
  875. );
  876. return $ret;
  877. }}
  878. // Same as above functions except these are for personal config storage and take an
  879. // additional $uid argument.
  880. if(! function_exists('set_pconfig')) {
  881. function set_pconfig($uid,$family,$key,$value) {
  882. global $a;
  883. $a->config[$uid][$family][$key] = $value;
  884. if(get_pconfig($uid,$family,$key,true) === false) {
  885. $ret = q("INSERT INTO `pconfig` ( `uid`, `cat`, `k`, `v` ) VALUES ( %d, '%s', '%s', '%s' ) ",
  886. intval($uid),
  887. dbesc($family),
  888. dbesc($key),
  889. dbesc($value)
  890. );
  891. if($ret)
  892. return $value;
  893. return $ret;
  894. }
  895. $ret = q("UPDATE `pconfig` SET `v` = '%s' WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
  896. intval($uid),
  897. dbesc($value),
  898. dbesc($family),
  899. dbesc($key)
  900. );
  901. if($ret)
  902. return $value;
  903. return $ret;
  904. }}
  905. if(! function_exists('del_pconfig')) {
  906. function del_pconfig($uid,$family,$key) {
  907. global $a;
  908. if(x($a->config[$uid][$family],$key))
  909. unset($a->config[$uid][$family][$key]);
  910. $ret = q("DELETE FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
  911. intval($uid),
  912. dbesc($cat),
  913. dbesc($key)
  914. );
  915. return $ret;
  916. }}
  917. // convert an XML document to a normalised, case-corrected array
  918. // used by webfinger
  919. if(! function_exists('convert_xml_element_to_array')) {
  920. function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
  921. // If we're getting too deep, bail out
  922. if ($recursion_depth > 512) {
  923. return(null);
  924. }
  925. if (!is_string($xml_element) &&
  926. !is_array($xml_element) &&
  927. (get_class($xml_element) == 'SimpleXMLElement')) {
  928. $xml_element_copy = $xml_element;
  929. $xml_element = get_object_vars($xml_element);
  930. }
  931. if (is_array($xml_element)) {
  932. $result_array = array();
  933. if (count($xml_element) <= 0) {
  934. return (trim(strval($xml_element_copy)));
  935. }
  936. foreach($xml_element as $key=>$value) {
  937. $recursion_depth++;
  938. $result_array[strtolower($key)] =
  939. convert_xml_element_to_array($value, $recursion_depth);
  940. $recursion_depth--;
  941. }
  942. if ($recursion_depth == 0) {
  943. $temp_array = $result_array;
  944. $result_array = array(
  945. strtolower($xml_element_copy->getName()) => $temp_array,
  946. );
  947. }
  948. return ($result_array);
  949. } else {
  950. return (trim(strval($xml_element)));
  951. }
  952. }}
  953. // Given an email style address, perform webfinger lookup and
  954. // return the resulting DFRN profile URL, or if no DFRN profile URL
  955. // is located, returns an OStatus subscription template (prefixed
  956. // with the string 'stat:' to identify it as on OStatus template).
  957. // If this isn't an email style address just return $s.
  958. // Return an empty string if email-style addresses but webfinger fails,
  959. // or if the resultant personal XRD doesn't contain a supported
  960. // subscription/friend-request attribute.
  961. if(! function_exists('webfinger_dfrn')) {
  962. function webfinger_dfrn($s) {
  963. if(! strstr($s,'@')) {
  964. return $s;
  965. }
  966. $links = webfinger($s);
  967. logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
  968. if(count($links)) {
  969. foreach($links as $link)
  970. if($link['@attributes']['rel'] === NAMESPACE_DFRN)
  971. return $link['@attributes']['href'];
  972. foreach($links as $link)
  973. if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
  974. return 'stat:' . $link['@attributes']['template'];
  975. }
  976. return '';
  977. }}
  978. // Given an email style address, perform webfinger lookup and
  979. // return the array of link attributes from the personal XRD file.
  980. // On error/failure return an empty array.
  981. if(! function_exists('webfinger')) {
  982. function webfinger($s) {
  983. $host = '';
  984. if(strstr($s,'@')) {
  985. $host = substr($s,strpos($s,'@') + 1);
  986. }
  987. if(strlen($host)) {
  988. $tpl = fetch_lrdd_template($host);
  989. logger('webfinger: lrdd template: ' . $tpl);
  990. if(strlen($tpl)) {
  991. $pxrd = str_replace('{uri}', urlencode('acct:'.$s), $tpl);
  992. $links = fetch_xrd_links($pxrd);
  993. if(! count($links)) {
  994. // try with double slashes
  995. $pxrd = str_replace('{uri}', urlencode('acct://'.$s), $tpl);
  996. $links = fetch_xrd_links($pxrd);
  997. }
  998. return $links;
  999. }
  1000. }
  1001. return array();
  1002. }}
  1003. if(! function_exists('lrdd')) {
  1004. function lrdd($uri) {
  1005. $a = get_app();
  1006. if(strstr($uri,'@')) {
  1007. return(webfinger($uri));
  1008. }
  1009. else {
  1010. $html = fetch_url($uri);
  1011. $headers = $a->get_curl_headers();
  1012. $lines = explode("\n",$headers);
  1013. if(count($lines)) {
  1014. foreach($lines as $line) {
  1015. // TODO alter the following regex to support multiple relations (space separated)
  1016. if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
  1017. $link = $matches[1];
  1018. break;
  1019. }
  1020. }
  1021. }
  1022. if(! isset($link)) {
  1023. // parse the page of the supplied URL looking for rel links
  1024. require_once('library/HTML5/Parser.php');
  1025. $dom = HTML5_Parser::parse($html);
  1026. if($dom) {
  1027. $items = $dom->getElementsByTagName('link');
  1028. foreach($items as $item) {
  1029. $x = $item->getAttribute('rel');
  1030. if($x == "lrdd") {
  1031. $link = $item->getAttribute('href');
  1032. break;
  1033. }
  1034. }
  1035. }
  1036. }
  1037. if(isset($link))
  1038. return(fetch_xrd_links($link));
  1039. }
  1040. return array();
  1041. }}
  1042. // Given a host name, locate the LRDD template from that
  1043. // host. Returns the LRDD template or an empty string on
  1044. // error/failure.
  1045. if(! function_exists('fetch_lrdd_template')) {
  1046. function fetch_lrdd_template($host) {
  1047. $tpl = '';
  1048. $url = 'http://' . $host . '/.well-known/host-meta' ;
  1049. $links = fetch_xrd_links($url);
  1050. if(count($links)) {
  1051. foreach($links as $link)
  1052. if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
  1053. $tpl = $link['@attributes']['template'];
  1054. }
  1055. if(! strpos($tpl,'{uri}'))
  1056. $tpl = '';
  1057. return $tpl;
  1058. }}
  1059. // Given a URL, retrieve the page as an XRD document.
  1060. // Return an array of links.
  1061. // on error/failure return empty array.
  1062. if(! function_exists('fetch_xrd_links')) {
  1063. function fetch_xrd_links($url) {
  1064. $xml = fetch_url($url);
  1065. if (! $xml)
  1066. return array();
  1067. logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
  1068. $h = simplexml_load_string($xml);
  1069. $arr = convert_xml_element_to_array($h);
  1070. if(isset($arr['xrd']['link'])) {
  1071. $link = $arr['xrd']['link'];
  1072. if(! isset($link[0]))
  1073. $links = array($link);
  1074. else
  1075. $links = $link;
  1076. return $links;
  1077. }
  1078. return array();
  1079. }}
  1080. // Convert an ACL array to a storable string
  1081. if(! function_exists('perms2str')) {
  1082. function perms2str($p) {
  1083. $ret = '';
  1084. $tmp = $p;
  1085. if(is_array($tmp)) {
  1086. array_walk($tmp,'sanitise_acl');
  1087. $ret = implode('',$tmp);
  1088. }
  1089. return $ret;
  1090. }}
  1091. // generate a guaranteed unique (for this domain) item ID for ATOM
  1092. // safe from birthday paradox
  1093. if(! function_exists('item_new_uri')) {
  1094. function item_new_uri($hostname,$uid) {
  1095. do {
  1096. $dups = false;
  1097. $hash = random_string();
  1098. $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
  1099. $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
  1100. dbesc($uri));
  1101. if(count($r))
  1102. $dups = true;
  1103. } while($dups == true);
  1104. return $uri;
  1105. }}
  1106. // Generate a guaranteed unique photo ID.
  1107. // safe from birthday paradox
  1108. if(! function_exists('photo_new_resource')) {
  1109. function photo_new_resource() {
  1110. do {
  1111. $found = false;
  1112. $resource = hash('md5',uniqid(mt_rand(),true));
  1113. $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
  1114. dbesc($resource)
  1115. );
  1116. if(count($r))
  1117. $found = true;
  1118. } while($found == true);
  1119. return $resource;
  1120. }}
  1121. // Take a URL from the wild, prepend http:// if necessary
  1122. // and check DNS to see if it's real
  1123. // return true if it's OK, false if something is wrong with it
  1124. if(! function_exists('validate_url')) {
  1125. function validate_url(&$url) {
  1126. if(substr($url,0,4) != 'http')
  1127. $url = 'http://' . $url;
  1128. $h = parse_url($url);
  1129. if(($h) && (checkdnsrr($h['host'], 'ANY'))) {
  1130. return true;
  1131. }
  1132. return false;
  1133. }}
  1134. // checks that email is an actual resolvable internet address
  1135. if(! function_exists('validate_email')) {
  1136. function validate_email($addr) {
  1137. if(! strpos($addr,'@'))
  1138. return false;
  1139. $h = substr($addr,strpos($addr,'@') + 1);
  1140. if(($h) && (checkdnsrr($h, 'ANY'))) {
  1141. return true;
  1142. }
  1143. return false;
  1144. }}
  1145. // Check $url against our list of allowed sites,
  1146. // wildcards allowed. If allowed_sites is unset return true;
  1147. // If url is allowed, return true.
  1148. // otherwise, return false
  1149. if(! function_exists('allowed_url')) {
  1150. function allowed_url($url) {
  1151. $h = parse_url($url);
  1152. if(! $h) {
  1153. return false;
  1154. }
  1155. $str_allowed = get_config('system','allowed_sites');
  1156. if(! $str_allowed)
  1157. return true;
  1158. $found = false;
  1159. $host = strtolower($h['host']);
  1160. // always allow our own site
  1161. if($host == strtolower($_SERVER['SERVER_NAME']))
  1162. return true;
  1163. $fnmatch = function_exists('fnmatch');
  1164. $allowed = explode(',',$str_allowed);
  1165. if(count($allowed)) {
  1166. foreach($allowed as $a) {
  1167. $pat = strtolower(trim($a));
  1168. if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
  1169. $found = true;
  1170. break;
  1171. }
  1172. }
  1173. }
  1174. return $found;
  1175. }}
  1176. // check if email address is allowed to register here.
  1177. // Compare against our list (wildcards allowed).
  1178. // Returns false if not allowed, true if allowed or if
  1179. // allowed list is not configured.
  1180. if(! function_exists('allowed_email')) {
  1181. function allowed_email($email) {
  1182. $domain = strtolower(substr($email,strpos($email,'@') + 1));
  1183. if(! $domain)
  1184. return false;
  1185. $str_allowed = get_config('system','allowed_email');
  1186. if(! $str_allowed)
  1187. return true;
  1188. $found = false;
  1189. $fnmatch = function_exists('fnmatch');
  1190. $allowed = explode(',',$str_allowed);
  1191. if(count($allowed)) {
  1192. foreach($allowed as $a) {
  1193. $pat = strtolower(trim($a));
  1194. if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
  1195. $found = true;
  1196. break;
  1197. }
  1198. }
  1199. }
  1200. return $found;
  1201. }}
  1202. // Format the like/dislike text for a profile item
  1203. // $cnt = number of people who like/dislike the item
  1204. // $arr = array of pre-linked names of likers/dislikers
  1205. // $type = one of 'like, 'dislike'
  1206. // $id = item id
  1207. // returns formatted text
  1208. if(! function_exists('format_like')) {
  1209. function format_like($cnt,$arr,$type,$id) {
  1210. $o = '';
  1211. if($cnt == 1)
  1212. $o .= $arr[0] . (($type === 'like') ? t(' likes this.') : t(' doesn\'t like this.')) . EOL ;
  1213. else {
  1214. $o .= '<span class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');" >'
  1215. . $cnt . ' ' . t('people') . '</span> ' . (($type === 'like') ? t('like this.') : t('don\'t like this.')) . EOL ;
  1216. $total = count($arr);
  1217. if($total >= MAX_LIKERS)
  1218. $arr = array_slice($arr, 0, MAX_LIKERS - 1);
  1219. if($total < MAX_LIKERS)
  1220. $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1];
  1221. $str = implode(', ', $arr);
  1222. if($total >= MAX_LIKERS)
  1223. $str .= t(', and ') . $total - MAX_LIKERS . t(' other people');
  1224. $str .= (($type === 'like') ? t(' like this.') : t(' don\'t like this.'));
  1225. $o .= "\t" . '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>';
  1226. }
  1227. return $o;
  1228. }}
  1229. // wrapper to load a view template, checking for alternate
  1230. // languages before falling back to the default
  1231. if(! function_exists('load_view_file')) {
  1232. function load_view_file($s) {
  1233. $b = basename($s);
  1234. $d = dirname($s);
  1235. $lang = get_config('system','language');
  1236. if($lang === false)
  1237. $lang = 'en';
  1238. if(file_exists("$d/$lang/$b"))
  1239. return file_get_contents("$d/$lang/$b");
  1240. return file_get_contents($s);
  1241. }}
  1242. // for html,xml parsing - let's say you've got
  1243. // an attribute foobar="class1 class2 class3"
  1244. // and you want to find out if it contains 'class3'.
  1245. // you can't use a normal sub string search because you
  1246. // might match 'notclass3' and a regex to do the job is
  1247. // possible but a bit complicated.
  1248. // pass the attribute string as $attr and the attribute you
  1249. // are looking for as $s - returns true if found, otherwise false
  1250. if(! function_exists('attribute_contains')) {
  1251. function attribute_contains($attr,$s) {
  1252. $a = explode(' ', $attr);
  1253. if(count($a) && in_array($s,$a))
  1254. return true;
  1255. return false;
  1256. }}
  1257. if(! function_exists('logger')) {
  1258. function logger($msg,$level = 0) {
  1259. $debugging = get_config('system','debugging');
  1260. $loglevel = intval(get_config('system','loglevel'));
  1261. $logfile = get_config('system','logfile');
  1262. if((! $debugging) || (! $logfile) || ($level > $loglevel))
  1263. return;
  1264. @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
  1265. return;
  1266. }}
  1267. if(! function_exists('activity_match')) {
  1268. function activity_match($haystack,$needle) {
  1269. if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
  1270. return true;
  1271. return false;
  1272. }}
  1273. // Pull out all #hashtags and @person tags from $s;
  1274. // We also get @person@domain.com - which would make
  1275. // the regex quite complicated as tags can also
  1276. // end a sentence. So we'll run through our results
  1277. // and strip the period from any tags which end with one.
  1278. // Returns array of tags found, or empty array.
  1279. if(! function_exists('get_tags')) {
  1280. function get_tags($s) {
  1281. $ret = array();
  1282. if(preg_match_all('/([@#][^ \x0D\x0A,:?]*)([ \x0D\x0A,:?]|$)/',$s,$match)) {
  1283. foreach($match[1] as $match) {
  1284. if(strstr($match,"]")) {
  1285. // we might be inside a bbcode color tag - leave it alone
  1286. continue;
  1287. }
  1288. if(substr($match,-1,1) === '.')
  1289. $ret[] = substr($match,0,-1);
  1290. else
  1291. $ret[] = $match;
  1292. }
  1293. }
  1294. return $ret;
  1295. }}
  1296. // quick and dirty quoted_printable encoding
  1297. if(! function_exists('qp')) {
  1298. function qp($s) {
  1299. return str_replace ("%","=",rawurlencode($s));
  1300. }}
  1301. if(! function_exists('like_puller')) {
  1302. function like_puller($a,$item,&$arr,$mode) {
  1303. $url = '';
  1304. $sparkle = '';
  1305. $verb = (($mode === 'like') ? ACTIVITY_LIKE : ACTIVITY_DISLIKE);
  1306. if((activity_match($item['verb'],$verb)) && ($item['id'] != $item['parent'])) {
  1307. $url = $item['author-link'];
  1308. if(($item['network'] === 'dfrn') && (! $item['self']) && ($item['author-link'] == $item['url'])) {
  1309. $url = $a->get_baseurl() . '/redir/' . $item['contact-id'];
  1310. $sparkle = ' class="sparkle" ';
  1311. }
  1312. if(! ((isset($arr[$item['parent'] . '-l'])) && (is_array($arr[$item['parent'] . '-l']))))
  1313. $arr[$item['parent'] . '-l'] = array();
  1314. if(! isset($arr[$item['parent']]))
  1315. $arr[$item['parent']] = 1;
  1316. else
  1317. $arr[$item['parent']] ++;
  1318. $arr[$item['parent'] . '-l'][] = '<a href="'. $url . '"'. $sparkle .'>' . $item['author-name'] . '</a>';
  1319. }
  1320. return;
  1321. }}
  1322. if(! function_exists('get_mentions')) {
  1323. function get_mentions($item) {
  1324. $o = '';
  1325. if(! strlen($item['tag']))
  1326. return $o;
  1327. $arr = explode(',',$item['tag']);
  1328. foreach($arr as $x) {
  1329. $matches = null;
  1330. if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches))
  1331. $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
  1332. }
  1333. return $o;
  1334. }}
  1335. if(! function_exists('contact_block')) {
  1336. function contact_block() {
  1337. $o = '';
  1338. $a = get_app();
  1339. if((! is_array($a->profile)) || ($a->profile['hide-friends']))
  1340. return $o;
  1341. $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
  1342. intval($a->profile['uid'])
  1343. );
  1344. if(count($r)) {
  1345. $total = intval($r[0]['total']);
  1346. }
  1347. if(! $total) {
  1348. $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
  1349. return $o;
  1350. }
  1351. $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT 24",
  1352. intval($a->profile['uid'])
  1353. );
  1354. if(count($r)) {
  1355. $o .= '<h4 class="contact-h4">' . $total . ' ' . t('Contacts') . '</h4><div id="contact-block">';
  1356. foreach($r as $rr) {
  1357. $redirect_url = $a->get_baseurl() . '/redir/' . $rr['id'];
  1358. if(local_user() && ($rr['uid'] == local_user())
  1359. && ($rr['network'] === 'dfrn')) {
  1360. $url = $redirect_url;
  1361. $sparkle = ' sparkle';
  1362. }
  1363. else {
  1364. $url = $rr['url'];
  1365. $sparkle = '';
  1366. }
  1367. $o .= '<div class="contact-block-div"><a class="contact-block-link' . $sparkle . '" href="' . $url . '" ><img class="contact-block-img' . $sparkle . '" src="' . $rr['micro'] . '" title="' . $rr['name'] . ' [' . $rr['url'] . ']" alt="' . $rr['name'] . '" /></a></div>' . "\r\n";
  1368. }
  1369. $o .= '</div><div id="contact-block-end"></div>';
  1370. $o .= '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
  1371. }
  1372. return $o;
  1373. }}
  1374. if(! function_exists('search')) {
  1375. function search($s) {
  1376. $a = get_app();
  1377. $o = '<div id="search-box">';
  1378. $o .= '<form action="' . $a->get_baseurl() . '/search' . '" method="get" >';
  1379. $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
  1380. $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />';
  1381. $o .= '</form></div>';
  1382. return $o;
  1383. }}
  1384. if(! function_exists('valid_email')) {
  1385. function valid_email($x){
  1386. if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
  1387. return true;
  1388. return false;
  1389. }}
  1390. if(! function_exists('gravatar_img')) {
  1391. function gravatar_img($email) {
  1392. $size = 175;
  1393. $opt = 'identicon'; // psuedo-random geometric pattern if not found
  1394. $rating = 'pg';
  1395. $hash = md5(trim(strtolower($email)));
  1396. $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg'
  1397. . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
  1398. logger('gravatar: ' . $email . ' ' . $url);
  1399. return $url;
  1400. }}
  1401. if(! function_exists('aes_decrypt')) {
  1402. function aes_decrypt($val,$ky)
  1403. {
  1404. $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
  1405. for($a=0;$a<strlen($ky);$a++)
  1406. $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
  1407. $mode = MCRYPT_MODE_ECB;
  1408. $enc = MCRYPT_RIJNDAEL_128;
  1409. $dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) );
  1410. return rtrim($dec,(( ord(substr($dec,strlen($dec)-1,1))>=0 and ord(substr($dec, strlen($dec)-1,1))<=16)? chr(ord( substr($dec,strlen($dec)-1,1))):null));
  1411. }}
  1412. if(! function_exists('aes_encrypt')) {
  1413. function aes_encrypt($val,$ky)
  1414. {
  1415. $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
  1416. for($a=0;$a<strlen($ky);$a++)
  1417. $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
  1418. $mode=MCRYPT_MODE_ECB;
  1419. $enc=MCRYPT_RIJNDAEL_128;
  1420. $val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16)));
  1421. return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM));
  1422. }}
  1423. /**
  1424. *
  1425. * Function: linkify
  1426. *
  1427. * Replace naked text hyperlink with HTML formatted hyperlink
  1428. *
  1429. */
  1430. if(! function_exists('linkify')) {
  1431. function linkify($s) {
  1432. $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\.\=\_\~\#\'\%]*)/", ' <a href="$1" >$1</a>', $s);
  1433. return($s);
  1434. }}
  1435. /**
  1436. *
  1437. * Function: smilies
  1438. *
  1439. * Description:
  1440. * Replaces text emoticons with graphical images
  1441. *
  1442. * @Parameter: string $s
  1443. *
  1444. * Returns string
  1445. */
  1446. if(! function_exists('smilies')) {
  1447. function smilies($s) {
  1448. $a = get_app();
  1449. return str_replace(
  1450. array( ':-)', ';-)', ':-(', ':(', ':-P', ':-"', ':-x', ':-X', ':-D', '8-|', '8-O'),
  1451. array(
  1452. '<img src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
  1453. '<img src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
  1454. '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
  1455. '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":(" />',
  1456. '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
  1457. '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
  1458. '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
  1459. '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
  1460. '<img src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
  1461. '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
  1462. '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />'
  1463. ), $s);
  1464. }}
  1465. /**
  1466. *
  1467. * Function : profile_load
  1468. * @parameter App $a
  1469. * @parameter string $nickname
  1470. * @parameter int $profile
  1471. *
  1472. * Summary: Loads a profile into the page sidebar.
  1473. * The function requires a writeable copy of the main App structure, and the nickname
  1474. * of a registered local account.
  1475. *
  1476. * If the viewer is an authenticated remote viewer, the profile displayed is the
  1477. * one that has been configured for his/her viewing in the Contact manager.
  1478. * Passing a non-zero profile ID can also allow a preview of a selected profile
  1479. * by the owner.
  1480. *
  1481. * Profile information is placed in the App structure for later retrieval.
  1482. * Honours the owner's chosen theme for display.
  1483. *
  1484. */
  1485. if(! function_exists('profile_load')) {
  1486. function profile_load(&$a, $nickname, $profile = 0) {
  1487. if(remote_user()) {
  1488. $r = q("SELECT `profile-id` FROM `contact` WHERE `id` = %d LIMIT 1",
  1489. intval($_SESSION['visitor_id']));
  1490. if(count($r))
  1491. $profile = $r[0]['profile-id'];
  1492. }
  1493. $r = null;
  1494. if($profile) {
  1495. $profile_int = intval($profile);
  1496. $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile`
  1497. LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
  1498. WHERE `user`.`nickname` = '%s' AND `profile`.`id` = %d LIMIT 1",
  1499. dbesc($nickname),
  1500. intval($profile_int)
  1501. );
  1502. }
  1503. if(! count($r)) {
  1504. $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile`
  1505. LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
  1506. WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 LIMIT 1",
  1507. dbesc($nickname)
  1508. );
  1509. }
  1510. if(($r === false) || (! count($r))) {
  1511. notice( t('No profile') . EOL );
  1512. $a->error = 404;
  1513. return;
  1514. }
  1515. $a->profile = $r[0];
  1516. $a->page['title'] = $a->profile['name'];
  1517. $_SESSION['theme'] = $a->profile['theme'];
  1518. if(! (x($a->page,'aside')))
  1519. $a->page['aside'] = '';
  1520. $a->page['aside'] .= profile_sidebar($a->profile);
  1521. $a->page['aside'] .= contact_block();
  1522. return;
  1523. }}
  1524. /**
  1525. *
  1526. * Function: profile_sidebar
  1527. *
  1528. * Formats a profile for display in the sidebar.
  1529. * It is very difficult to templatise the HTML completely
  1530. * because of all the conditional logic.
  1531. *
  1532. * @parameter: array $profile
  1533. *
  1534. * Returns HTML string stuitable for sidebar inclusion
  1535. * Exceptions: Returns empty string if passed $profile is wrong type or not populated
  1536. *
  1537. */
  1538. if(! function_exists('profile_sidebar')) {
  1539. function profile_sidebar($profile) {
  1540. $o = '';
  1541. $location = '';
  1542. $address = false;
  1543. if((! is_array($profile)) && (! count($profile)))
  1544. return $o;
  1545. $fullname = '<div class="fn">' . $profile['name'] . '</div>';
  1546. $tabs = '';
  1547. $photo = '<div id="profile=photo-wrapper"><img class="photo" src="' . $profile['photo'] . '" alt="' . $profile['name'] . '" /></div>';
  1548. $connect = (($profile['uid'] != local_user()) ? '<li><a id="dfrn-request-link" href="dfrn_request/' . $profile['nickname'] . '">' . t('Connect') . '</a></li>' : '');
  1549. if((x($profile,'address') == 1)
  1550. || (x($profile,'locality') == 1)
  1551. || (x($profile,'region') == 1)
  1552. || (x($profile,'postal-code') == 1)
  1553. || (x($profile,'country-name') == 1))
  1554. $address = true;
  1555. if($address) {
  1556. $location .= '<div class="location"><span class="location-label">' . t('Location:') . '</span> <div class="adr">';
  1557. $location .= ((x($profile,'address') == 1) ? '<div class="street-address">' . $profile['address'] . '</div>' : '');
  1558. $location .= (((x($profile,'locality') == 1) || (x($profile,'region') == 1) || (x($profile,'postal-code') == 1))
  1559. ? '<span class="city-state-zip"><span class="locality">' . $profile['locality'] . '</span>'
  1560. . ((x($profile['locality']) == 1) ? t(', ') : '')
  1561. . '<span class="region">' . $profile['region'] . '</span>'
  1562. . ' <span class="postal-code">' . $profile['postal-code'] . '</span></span>' : '');
  1563. $location .= ((x($profile,'country-name') == 1) ? ' <span class="country-name">' . $profile['country-name'] . '</span>' : '');
  1564. $location .= '</div></div><div class="profile-clear"></div>';
  1565. }
  1566. $gender = ((x($profile,'gender') == 1) ? '<div class="mf"><span class="gender-label">' . t('Gender:') . '</span> <span class="x-gender">' . $profile['gender'] . '</span></div><div class="profile-clear"></div>' : '');
  1567. $pubkey = ((x($profile,'key') == 1) ? '<div class="key" style="display:none;">' . $profile['pubkey'] . '</div>' : '');
  1568. $marital = ((x($profile,'marital') == 1) ? '<div class="marital"><span class="marital-label"><span class="heart">&hearts;</span> ' . t('Status:') . ' </span><span class="marital-text">' . $profile['marital'] . '</span></div></div><div class="profile-clear"></div>' : '');
  1569. $homepage = ((x($profile,'homepage') == 1) ? '<div class="homepage"><span class="homepage-label">' . t('Homepage:') . ' </span><span class="homepage-url">' . linkify($profile['homepage']) . '</span></div></div><div class="profile-clear"></div>' : '');
  1570. $tpl = load_view_file('view/profile_vcard.tpl');
  1571. $o .= replace_macros($tpl, array(
  1572. '$fullname' => $fullname,
  1573. '$tabs' => $tabs,
  1574. '$photo' => $photo,
  1575. '$connect' => $connect,
  1576. '$location' => $location,
  1577. '$gender' => $gender,
  1578. '$pubkey' => $pubkey,
  1579. '$marital' => $marital,
  1580. '$homepage' => $homepage
  1581. ));
  1582. return $o;
  1583. }}