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.

2250 lines
74 KiB

11 years ago
11 years ago
9 years ago
5 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. <?php
  2. /**
  3. * @file include/items.php
  4. */
  5. use \Friendica\ParseUrl;
  6. require_once('include/bbcode.php');
  7. require_once('include/oembed.php');
  8. require_once('include/salmon.php');
  9. require_once('include/crypto.php');
  10. require_once('include/Photo.php');
  11. require_once('include/tags.php');
  12. require_once('include/files.php');
  13. require_once('include/text.php');
  14. require_once('include/email.php');
  15. require_once('include/threads.php');
  16. require_once('include/socgraph.php');
  17. require_once('include/plaintext.php');
  18. require_once('include/ostatus.php');
  19. require_once('include/feed.php');
  20. require_once('include/Contact.php');
  21. require_once('mod/share.php');
  22. require_once('include/enotify.php');
  23. require_once('include/dfrn.php');
  24. require_once('include/group.php');
  25. require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
  26. function construct_verb($item) {
  27. if ($item['verb'])
  28. return $item['verb'];
  29. return ACTIVITY_POST;
  30. }
  31. /* limit_body_size()
  32. *
  33. * The purpose of this function is to apply system message length limits to
  34. * imported messages without including any embedded photos in the length
  35. */
  36. if (! function_exists('limit_body_size')) {
  37. function limit_body_size($body) {
  38. // logger('limit_body_size: start', LOGGER_DEBUG);
  39. $maxlen = get_max_import_size();
  40. // If the length of the body, including the embedded images, is smaller
  41. // than the maximum, then don't waste time looking for the images
  42. if ($maxlen && (strlen($body) > $maxlen)) {
  43. logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
  44. $orig_body = $body;
  45. $new_body = '';
  46. $textlen = 0;
  47. $max_found = false;
  48. $img_start = strpos($orig_body, '[img');
  49. $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
  50. $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
  51. while(($img_st_close !== false) && ($img_end !== false)) {
  52. $img_st_close++; // make it point to AFTER the closing bracket
  53. $img_end += $img_start;
  54. $img_end += strlen('[/img]');
  55. if (! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
  56. // This is an embedded image
  57. if ( ($textlen + $img_start) > $maxlen ) {
  58. if ($textlen < $maxlen) {
  59. logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
  60. $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
  61. $textlen = $maxlen;
  62. }
  63. } else {
  64. $new_body = $new_body . substr($orig_body, 0, $img_start);
  65. $textlen += $img_start;
  66. }
  67. $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
  68. } else {
  69. if ( ($textlen + $img_end) > $maxlen ) {
  70. if ($textlen < $maxlen) {
  71. logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
  72. $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
  73. $textlen = $maxlen;
  74. }
  75. } else {
  76. $new_body = $new_body . substr($orig_body, 0, $img_end);
  77. $textlen += $img_end;
  78. }
  79. }
  80. $orig_body = substr($orig_body, $img_end);
  81. if ($orig_body === false) // in case the body ends on a closing image tag
  82. $orig_body = '';
  83. $img_start = strpos($orig_body, '[img');
  84. $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
  85. $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
  86. }
  87. if ( ($textlen + strlen($orig_body)) > $maxlen) {
  88. if ($textlen < $maxlen) {
  89. logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
  90. $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
  91. $textlen = $maxlen;
  92. }
  93. } else {
  94. logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
  95. $new_body = $new_body . $orig_body;
  96. $textlen += strlen($orig_body);
  97. }
  98. return $new_body;
  99. } else
  100. return $body;
  101. }}
  102. function title_is_body($title, $body) {
  103. $title = strip_tags($title);
  104. $title = trim($title);
  105. $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
  106. $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
  107. $body = strip_tags($body);
  108. $body = trim($body);
  109. $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
  110. $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
  111. if (strlen($title) < strlen($body))
  112. $body = substr($body, 0, strlen($title));
  113. if (($title != $body) and (substr($title, -3) == "...")) {
  114. $pos = strrpos($title, "...");
  115. if ($pos > 0) {
  116. $title = substr($title, 0, $pos);
  117. $body = substr($body, 0, $pos);
  118. }
  119. }
  120. return($title == $body);
  121. }
  122. function add_page_info_data($data) {
  123. call_hooks('page_info_data', $data);
  124. // It maybe is a rich content, but if it does have everything that a link has,
  125. // then treat it that way
  126. if (($data["type"] == "rich") AND is_string($data["title"]) AND
  127. is_string($data["text"]) AND (sizeof($data["images"]) > 0)) {
  128. $data["type"] = "link";
  129. }
  130. if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $data["url"])) {
  131. return "";
  132. }
  133. if ($no_photos AND ($data["type"] == "photo")) {
  134. return "";
  135. }
  136. if (sizeof($data["images"]) > 0) {
  137. $preview = $data["images"][0];
  138. } else {
  139. $preview = "";
  140. }
  141. // Escape some bad characters
  142. $data["url"] = str_replace(array("[", "]"), array("&#91;", "&#93;"), htmlentities($data["url"], ENT_QUOTES, 'UTF-8', false));
  143. $data["title"] = str_replace(array("[", "]"), array("&#91;", "&#93;"), htmlentities($data["title"], ENT_QUOTES, 'UTF-8', false));
  144. $text = "[attachment type='".$data["type"]."'";
  145. if ($data["text"] == "") {
  146. $data["text"] = $data["title"];
  147. }
  148. if ($data["text"] == "") {
  149. $data["text"] = $data["url"];
  150. }
  151. if ($data["url"] != "") {
  152. $text .= " url='".$data["url"]."'";
  153. }
  154. if ($data["title"] != "") {
  155. $text .= " title='".$data["title"]."'";
  156. }
  157. if (sizeof($data["images"]) > 0) {
  158. $preview = str_replace(array("[", "]"), array("&#91;", "&#93;"), htmlentities($data["images"][0]["src"], ENT_QUOTES, 'UTF-8', false));
  159. // if the preview picture is larger than 500 pixels then show it in a larger mode
  160. // But only, if the picture isn't higher than large (To prevent huge posts)
  161. if (($data["images"][0]["width"] >= 500) AND ($data["images"][0]["width"] >= $data["images"][0]["height"])) {
  162. $text .= " image='".$preview."'";
  163. } else {
  164. $text .= " preview='".$preview."'";
  165. }
  166. }
  167. $text .= "]".$data["text"]."[/attachment]";
  168. $hashtags = "";
  169. if (isset($data["keywords"]) AND count($data["keywords"])) {
  170. $a = get_app();
  171. $hashtags = "\n";
  172. foreach ($data["keywords"] AS $keyword) {
  173. /// @todo make a positive list of allowed characters
  174. $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "", "`", "(", ")", "", ""),
  175. array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
  176. $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
  177. }
  178. }
  179. return "\n".$text.$hashtags;
  180. }
  181. function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
  182. $data = ParseUrl::getSiteinfoCached($url, true);
  183. if ($photo != "")
  184. $data["images"][0]["src"] = $photo;
  185. logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
  186. if (!$keywords AND isset($data["keywords"]))
  187. unset($data["keywords"]);
  188. if (($keyword_blacklist != "") AND isset($data["keywords"])) {
  189. $list = explode(",", $keyword_blacklist);
  190. foreach ($list AS $keyword) {
  191. $keyword = trim($keyword);
  192. $index = array_search($keyword, $data["keywords"]);
  193. if ($index !== false)
  194. unset($data["keywords"][$index]);
  195. }
  196. }
  197. return($data);
  198. }
  199. function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
  200. $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
  201. $tags = "";
  202. if (isset($data["keywords"]) AND count($data["keywords"])) {
  203. $a = get_app();
  204. foreach ($data["keywords"] AS $keyword) {
  205. $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
  206. array("","", "", "", "", ""), $keyword);
  207. if ($tags != "")
  208. $tags .= ",";
  209. $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
  210. }
  211. }
  212. return($tags);
  213. }
  214. function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
  215. $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
  216. $text = add_page_info_data($data);
  217. return($text);
  218. }
  219. function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
  220. logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
  221. $URLSearchString = "^\[\]";
  222. // Fix for Mastodon where the mentions are in a different format
  223. $body = preg_replace("/\[url\=([$URLSearchString]*)\]([#!@])(.*?)\[\/url\]/ism",
  224. '$2[url=$1]$3[/url]', $body);
  225. // Adding these spaces is a quick hack due to my problems with regular expressions :)
  226. preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
  227. if (!$matches)
  228. preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
  229. // Convert urls without bbcode elements
  230. if (!$matches AND $texturl) {
  231. preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
  232. // Yeah, a hack. I really hate regular expressions :)
  233. if ($matches)
  234. $matches[1] = $matches[2];
  235. }
  236. if ($matches)
  237. $footer = add_page_info($matches[1], $no_photos);
  238. // Remove the link from the body if the link is attached at the end of the post
  239. if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
  240. $removedlink = trim(str_replace($matches[1], "", $body));
  241. if (($removedlink == "") OR strstr($body, $removedlink))
  242. $body = $removedlink;
  243. $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
  244. $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
  245. if (($removedlink == "") OR strstr($body, $removedlink))
  246. $body = $removedlink;
  247. }
  248. // Add the page information to the bottom
  249. if (isset($footer) AND (trim($footer) != ""))
  250. $body .= $footer;
  251. return $body;
  252. }
  253. /**
  254. * Adds a "lang" specification in a "postopts" element of given $arr,
  255. * if possible and not already present.
  256. * Expects "body" element to exist in $arr.
  257. *
  258. * @todo Add a parameter to request forcing override
  259. */
  260. function item_add_language_opt(&$arr) {
  261. if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
  262. if ( x($arr, 'postopts') )
  263. {
  264. if ( strstr($arr['postopts'], 'lang=') )
  265. {
  266. // do not override
  267. /// @TODO Add parameter to request overriding
  268. return;
  269. }
  270. $postopts = $arr['postopts'];
  271. } else {
  272. $postopts = "";
  273. }
  274. require_once('library/langdet/Text/LanguageDetect.php');
  275. $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
  276. $l = new Text_LanguageDetect;
  277. //$lng = $l->detectConfidence($naked_body);
  278. //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
  279. $lng = $l->detect($naked_body, 3);
  280. if (sizeof($lng) > 0) {
  281. if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
  282. $postopts .= 'lang=';
  283. $sep = "";
  284. foreach ($lng as $language => $score) {
  285. $postopts .= $sep . $language.";".$score;
  286. $sep = ':';
  287. }
  288. $arr['postopts'] = $postopts;
  289. }
  290. }
  291. /**
  292. * @brief Creates an unique guid out of a given uri
  293. *
  294. * @param string $uri uri of an item entry
  295. * @param string $host (Optional) hostname for the GUID prefix
  296. * @return string unique guid
  297. */
  298. function uri_to_guid($uri, $host = "") {
  299. // Our regular guid routine is using this kind of prefix as well
  300. // We have to avoid that different routines could accidentally create the same value
  301. $parsed = parse_url($uri);
  302. if ($host == "") {
  303. $host = $parsed["host"];
  304. }
  305. $guid_prefix = hash("crc32", $host);
  306. // Remove the scheme to make sure that "https" and "http" doesn't make a difference
  307. unset($parsed["scheme"]);
  308. $host_id = implode("/", $parsed);
  309. // We could use any hash algorithm since it isn't a security issue
  310. $host_hash = hash("ripemd128", $host_id);
  311. return $guid_prefix.$host_hash;
  312. }
  313. function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
  314. $a = get_app();
  315. // If it is a posting where users should get notifications, then define it as wall posting
  316. if ($notify) {
  317. $arr['wall'] = 1;
  318. $arr['type'] = 'wall';
  319. $arr['origin'] = 1;
  320. $arr['last-child'] = 1;
  321. $arr['network'] = NETWORK_DFRN;
  322. // We have to avoid duplicates. So we create the GUID in form of a hash of the plink or uri.
  323. // In difference to the call to "uri_to_guid" several lines below we add the hash of our own host.
  324. // This is done because our host is the original creator of the post.
  325. if (isset($arr['plink'])) {
  326. $arr['guid'] = uri_to_guid($arr['plink'], $a->get_hostname());
  327. } elseif (isset($arr['uri'])) {
  328. $arr['guid'] = uri_to_guid($arr['uri'], $a->get_hostname());
  329. }
  330. }
  331. // If a Diaspora signature structure was passed in, pull it out of the
  332. // item array and set it aside for later storage.
  333. $dsprsig = null;
  334. if (x($arr,'dsprsig')) {
  335. $encoded_signature = $arr['dsprsig'];
  336. $dsprsig = json_decode(base64_decode($arr['dsprsig']));
  337. unset($arr['dsprsig']);
  338. }
  339. // Converting the plink
  340. if ($arr['network'] == NETWORK_OSTATUS) {
  341. if (isset($arr['plink']))
  342. $arr['plink'] = ostatus::convert_href($arr['plink']);
  343. elseif (isset($arr['uri']))
  344. $arr['plink'] = ostatus::convert_href($arr['uri']);
  345. }
  346. if (x($arr, 'gravity'))
  347. $arr['gravity'] = intval($arr['gravity']);
  348. elseif ($arr['parent-uri'] === $arr['uri'])
  349. $arr['gravity'] = 0;
  350. elseif (activity_match($arr['verb'],ACTIVITY_POST))
  351. $arr['gravity'] = 6;
  352. else
  353. $arr['gravity'] = 6; // extensible catchall
  354. if (! x($arr,'type'))
  355. $arr['type'] = 'remote';
  356. /* check for create date and expire time */
  357. $uid = intval($arr['uid']);
  358. $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
  359. if (dbm::is_result($r)) {
  360. $expire_interval = $r[0]['expire'];
  361. if ($expire_interval>0) {
  362. $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
  363. $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
  364. if ($created_date < $expire_date) {
  365. logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
  366. return 0;
  367. }
  368. }
  369. }
  370. // Do we already have this item?
  371. // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
  372. if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
  373. $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
  374. dbesc(trim($arr['uri'])),
  375. intval($uid),
  376. dbesc(NETWORK_DIASPORA),
  377. dbesc(NETWORK_DFRN),
  378. dbesc(NETWORK_OSTATUS)
  379. );
  380. if ($r) {
  381. // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
  382. if ($uid != 0)
  383. logger("Item with uri ".$arr['uri']." already existed for user ".$uid." with id ".$r[0]["id"]." target network ".$r[0]["network"]." - new network: ".$arr['network']);
  384. return($r[0]["id"]);
  385. }
  386. }
  387. // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
  388. // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
  389. //if ((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
  390. // $arr['body'] = strip_tags($arr['body']);
  391. item_add_language_opt($arr);
  392. if ($notify)
  393. $guid_prefix = "";
  394. elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
  395. $arr['guid'] = uri_to_guid($arr['plink']);
  396. elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
  397. $arr['guid'] = uri_to_guid($arr['uri']);
  398. else {
  399. $parsed = parse_url($arr["author-link"]);
  400. $guid_prefix = hash("crc32", $parsed["host"]);
  401. }
  402. $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
  403. $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
  404. $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : item_new_uri($a->get_hostname(), $uid, $arr['guid']));
  405. $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
  406. $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
  407. $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
  408. $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
  409. $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
  410. $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
  411. $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
  412. $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
  413. $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
  414. $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
  415. $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
  416. $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
  417. $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
  418. $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
  419. $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
  420. $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
  421. $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
  422. $arr['deleted'] = 0;
  423. $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : $arr['uri']);
  424. $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
  425. $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
  426. $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
  427. $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
  428. $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
  429. $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
  430. $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
  431. $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
  432. $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
  433. $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
  434. $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
  435. $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
  436. $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
  437. $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
  438. $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
  439. $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
  440. $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
  441. $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
  442. $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
  443. $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
  444. $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
  445. $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
  446. $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
  447. // Items cannot be stored before they happen ...
  448. if ($arr['created'] > datetime_convert())
  449. $arr['created'] = datetime_convert();
  450. // We haven't invented time travel by now.
  451. if ($arr['edited'] > datetime_convert())
  452. $arr['edited'] = datetime_convert();
  453. if (($arr['author-link'] == "") AND ($arr['owner-link'] == ""))
  454. logger("Both author-link and owner-link are empty. Called by: ".App::callstack(), LOGGER_DEBUG);
  455. if ($arr['plink'] == "") {
  456. $a = get_app();
  457. $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
  458. }
  459. if ($arr['network'] == "") {
  460. $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
  461. dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
  462. dbesc(normalise_link($arr['author-link'])),
  463. intval($arr['uid'])
  464. );
  465. if (!dbm::is_result($r))
  466. $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
  467. dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
  468. dbesc(normalise_link($arr['author-link']))
  469. );
  470. if (!dbm::is_result($r))
  471. $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
  472. intval($arr['contact-id']),
  473. intval($arr['uid'])
  474. );
  475. if (dbm::is_result($r))
  476. $arr['network'] = $r[0]["network"];
  477. // Fallback to friendica (why is it empty in some cases?)
  478. if ($arr['network'] == "")
  479. $arr['network'] = NETWORK_DFRN;
  480. logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
  481. }
  482. // The contact-id should be set before "item_store" was called - but there seems to be some issues
  483. if ($arr["contact-id"] == 0) {
  484. // First we are looking for a suitable contact that matches with the author of the post
  485. // This is done only for comments (See below explanation at "gcontact-id")
  486. if ($arr['parent-uri'] != $arr['uri'])
  487. $arr["contact-id"] = get_contact($arr['author-link'], $uid);
  488. // If not present then maybe the owner was found
  489. if ($arr["contact-id"] == 0)
  490. $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
  491. // Still missing? Then use the "self" contact of the current user
  492. if ($arr["contact-id"] == 0) {
  493. $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
  494. if ($r)
  495. $arr["contact-id"] = $r[0]["id"];
  496. }
  497. logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
  498. }
  499. if ($arr["gcontact-id"] == 0) {
  500. // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
  501. // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
  502. // On comments the author is the better choice.
  503. if ($arr['parent-uri'] === $arr['uri'])
  504. $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
  505. "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
  506. else
  507. $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
  508. "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
  509. }
  510. if ($arr["author-id"] == 0)
  511. $arr["author-id"] = get_contact($arr["author-link"], 0);
  512. if ($arr["owner-id"] == 0)
  513. $arr["owner-id"] = get_contact($arr["owner-link"], 0);
  514. if ($arr['guid'] != "") {
  515. // Checking if there is already an item with the same guid
  516. logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
  517. $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
  518. dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
  519. if (dbm::is_result($r)) {
  520. logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
  521. return 0;
  522. }
  523. }
  524. // Check for hashtags in the body and repair or add hashtag links
  525. item_body_set_hashtags($arr);
  526. $arr['thr-parent'] = $arr['parent-uri'];
  527. if ($arr['parent-uri'] === $arr['uri']) {
  528. $parent_id = 0;
  529. $parent_deleted = 0;
  530. $allow_cid = $arr['allow_cid'];
  531. $allow_gid = $arr['allow_gid'];
  532. $deny_cid = $arr['deny_cid'];
  533. $deny_gid = $arr['deny_gid'];
  534. $notify_type = 'wall-new';
  535. } else {
  536. // find the parent and snarf the item id and ACLs
  537. // and anything else we need to inherit
  538. $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
  539. dbesc($arr['parent-uri']),
  540. intval($arr['uid'])
  541. );
  542. if (dbm::is_result($r)) {
  543. // is the new message multi-level threaded?
  544. // even though we don't support it now, preserve the info
  545. // and re-attach to the conversation parent.
  546. if ($r[0]['uri'] != $r[0]['parent-uri']) {
  547. $arr['parent-uri'] = $r[0]['parent-uri'];
  548. $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
  549. ORDER BY `id` ASC LIMIT 1",
  550. dbesc($r[0]['parent-uri']),
  551. dbesc($r[0]['parent-uri']),
  552. intval($arr['uid'])
  553. );
  554. if ($z && count($z))
  555. $r = $z;
  556. }
  557. $parent_id = $r[0]['id'];
  558. $parent_deleted = $r[0]['deleted'];
  559. $allow_cid = $r[0]['allow_cid'];
  560. $allow_gid = $r[0]['allow_gid'];
  561. $deny_cid = $r[0]['deny_cid'];
  562. $deny_gid = $r[0]['deny_gid'];
  563. $arr['wall'] = $r[0]['wall'];
  564. $notify_type = 'comment-new';
  565. // if the parent is private, force privacy for the entire conversation
  566. // This differs from the above settings as it subtly allows comments from
  567. // email correspondents to be private even if the overall thread is not.
  568. if ($r[0]['private'])
  569. $arr['private'] = $r[0]['private'];
  570. // Edge case. We host a public forum that was originally posted to privately.
  571. // The original author commented, but as this is a comment, the permissions
  572. // weren't fixed up so it will still show the comment as private unless we fix it here.
  573. if ((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
  574. $arr['private'] = 0;
  575. // If its a post from myself then tag the thread as "mention"
  576. logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
  577. $u = q("SELECT `nickname` FROM `user` WHERE `uid` = %d", intval($arr['uid']));
  578. if (count($u)) {
  579. $a = get_app();
  580. $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
  581. logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
  582. if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
  583. q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
  584. logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
  585. }
  586. }