3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/threads.php');
13 require_once('include/socgraph.php');
14 require_once('include/plaintext.php');
15 require_once('include/ostatus.php');
16 require_once('include/feed.php');
17 require_once('include/Contact.php');
18 require_once('mod/share.php');
19 require_once('include/enotify.php');
20 require_once('include/dfrn.php');
21 require_once('include/group.php');
23 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
25 function construct_verb($item) {
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
36 if (! function_exists('limit_body_size')) {
37 function limit_body_size($body) {
39 // logger('limit_body_size: start', LOGGER_DEBUG);
41 $maxlen = get_max_import_size();
43 // If the length of the body, including the embedded images, is smaller
44 // than the maximum, then don't waste time looking for the images
45 if ($maxlen && (strlen($body) > $maxlen)) {
47 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
54 $img_start = strpos($orig_body, '[img');
55 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
56 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
57 while(($img_st_close !== false) && ($img_end !== false)) {
59 $img_st_close++; // make it point to AFTER the closing bracket
60 $img_end += $img_start;
61 $img_end += strlen('[/img]');
63 if (! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
64 // This is an embedded image
66 if ( ($textlen + $img_start) > $maxlen ) {
67 if ($textlen < $maxlen) {
68 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
69 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
73 $new_body = $new_body . substr($orig_body, 0, $img_start);
74 $textlen += $img_start;
77 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
80 if ( ($textlen + $img_end) > $maxlen ) {
81 if ($textlen < $maxlen) {
82 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
83 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
87 $new_body = $new_body . substr($orig_body, 0, $img_end);
91 $orig_body = substr($orig_body, $img_end);
93 if ($orig_body === false) // in case the body ends on a closing image tag
96 $img_start = strpos($orig_body, '[img');
97 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
98 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
101 if ( ($textlen + strlen($orig_body)) > $maxlen) {
102 if ($textlen < $maxlen) {
103 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
104 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
108 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
109 $new_body = $new_body . $orig_body;
110 $textlen += strlen($orig_body);
118 function title_is_body($title, $body) {
120 $title = strip_tags($title);
121 $title = trim($title);
122 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
123 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
125 $body = strip_tags($body);
127 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
128 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
130 if (strlen($title) < strlen($body))
131 $body = substr($body, 0, strlen($title));
133 if (($title != $body) and (substr($title, -3) == "...")) {
134 $pos = strrpos($title, "...");
136 $title = substr($title, 0, $pos);
137 $body = substr($body, 0, $pos);
141 return($title == $body);
144 function add_page_info_data($data) {
145 call_hooks('page_info_data', $data);
147 // It maybe is a rich content, but if it does have everything that a link has,
148 // then treat it that way
149 if (($data["type"] == "rich") AND is_string($data["title"]) AND
150 is_string($data["text"]) AND (sizeof($data["images"]) > 0)) {
151 $data["type"] = "link";
154 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $data["url"])) {
158 if ($no_photos AND ($data["type"] == "photo")) {
162 if (sizeof($data["images"]) > 0) {
163 $preview = $data["images"][0];
168 // Escape some bad characters
169 $data["url"] = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["url"], ENT_QUOTES, 'UTF-8', false));
170 $data["title"] = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["title"], ENT_QUOTES, 'UTF-8', false));
172 $text = "[attachment type='".$data["type"]."'";
174 if ($data["text"] == "") {
175 $data["text"] = $data["title"];
178 if ($data["text"] == "") {
179 $data["text"] = $data["url"];
182 if ($data["url"] != "") {
183 $text .= " url='".$data["url"]."'";
186 if ($data["title"] != "") {
187 $text .= " title='".$data["title"]."'";
190 if (sizeof($data["images"]) > 0) {
191 $preview = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["images"][0]["src"], ENT_QUOTES, 'UTF-8', false));
192 // if the preview picture is larger than 500 pixels then show it in a larger mode
193 // But only, if the picture isn't higher than large (To prevent huge posts)
194 if (($data["images"][0]["width"] >= 500) AND ($data["images"][0]["width"] >= $data["images"][0]["height"])) {
195 $text .= " image='".$preview."'";
197 $text .= " preview='".$preview."'";
201 $text .= "]".$data["text"]."[/attachment]";
204 if (isset($data["keywords"]) AND count($data["keywords"])) {
207 foreach ($data["keywords"] AS $keyword) {
208 /// @todo make a positive list of allowed characters
209 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"),
210 array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
211 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
215 return "\n".$text.$hashtags;
218 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
219 require_once("mod/parse_url.php");
221 $data = parseurl_getsiteinfo_cached($url, true);
224 $data["images"][0]["src"] = $photo;
226 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
228 if (!$keywords AND isset($data["keywords"]))
229 unset($data["keywords"]);
231 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
232 $list = explode(",", $keyword_blacklist);
233 foreach ($list AS $keyword) {
234 $keyword = trim($keyword);
235 $index = array_search($keyword, $data["keywords"]);
236 if ($index !== false)
237 unset($data["keywords"][$index]);
244 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
245 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
248 if (isset($data["keywords"]) AND count($data["keywords"])) {
250 foreach ($data["keywords"] AS $keyword) {
251 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
252 array("","", "", "", "", ""), $keyword);
257 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
264 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
265 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
267 $text = add_page_info_data($data);
272 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
274 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
276 $URLSearchString = "^\[\]";
278 // Adding these spaces is a quick hack due to my problems with regular expressions :)
279 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
282 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
284 // Convert urls without bbcode elements
285 if (!$matches AND $texturl) {
286 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
288 // Yeah, a hack. I really hate regular expressions :)
290 $matches[1] = $matches[2];
294 $footer = add_page_info($matches[1], $no_photos);
296 // Remove the link from the body if the link is attached at the end of the post
297 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
298 $removedlink = trim(str_replace($matches[1], "", $body));
299 if (($removedlink == "") OR strstr($body, $removedlink))
300 $body = $removedlink;
302 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
303 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
304 if (($removedlink == "") OR strstr($body, $removedlink))
305 $body = $removedlink;
308 // Add the page information to the bottom
309 if (isset($footer) AND (trim($footer) != ""))
316 * Adds a "lang" specification in a "postopts" element of given $arr,
317 * if possible and not already present.
318 * Expects "body" element to exist in $arr.
320 * @todo Add a parameter to request forcing override
322 function item_add_language_opt(&$arr) {
324 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
326 if ( x($arr, 'postopts') )
328 if ( strstr($arr['postopts'], 'lang=') )
331 /// @TODO Add parameter to request overriding
334 $postopts = $arr['postopts'];
339 require_once('library/langdet/Text/LanguageDetect.php');
340 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
341 $l = new Text_LanguageDetect;
342 //$lng = $l->detectConfidence($naked_body);
343 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
344 $lng = $l->detect($naked_body, 3);
346 if (sizeof($lng) > 0) {
347 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
348 $postopts .= 'lang=';
350 foreach ($lng as $language => $score) {
351 $postopts .= $sep . $language.";".$score;
354 $arr['postopts'] = $postopts;
359 * @brief Creates an unique guid out of a given uri
361 * @param string $uri uri of an item entry
362 * @return string unique guid
364 function uri_to_guid($uri, $host = "") {
366 // Our regular guid routine is using this kind of prefix as well
367 // We have to avoid that different routines could accidentally create the same value
368 $parsed = parse_url($uri);
371 $host = $parsed["host"];
374 $guid_prefix = hash("crc32", $host);
376 // Remove the scheme to make sure that "https" and "http" doesn't make a difference
377 unset($parsed["scheme"]);
379 $host_id = implode("/", $parsed);
381 // We could use any hash algorithm since it isn't a security issue
382 $host_hash = hash("ripemd128", $host_id);
384 return $guid_prefix.$host_hash;
387 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
391 // If it is a posting where users should get notifications, then define it as wall posting
394 $arr['type'] = 'wall';
396 $arr['last-child'] = 1;
397 $arr['network'] = NETWORK_DFRN;
399 // We have to avoid duplicates. So we create the GUID in form of a hash of the plink or uri.
400 // In difference to the call to "uri_to_guid" several lines below we add the hash of our own host.
401 // This is done because our host is the original creator of the post.
402 if (isset($arr['plink'])) {
403 $arr['guid'] = uri_to_guid($arr['plink'], $a->get_hostname());
404 } elseif (isset($arr['uri'])) {
405 $arr['guid'] = uri_to_guid($arr['uri'], $a->get_hostname());
409 // If a Diaspora signature structure was passed in, pull it out of the
410 // item array and set it aside for later storage.
413 if (x($arr,'dsprsig')) {
414 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
415 unset($arr['dsprsig']);
418 // Converting the plink
419 if ($arr['network'] == NETWORK_OSTATUS) {
420 if (isset($arr['plink']))
421 $arr['plink'] = ostatus::convert_href($arr['plink']);
422 elseif (isset($arr['uri']))
423 $arr['plink'] = ostatus::convert_href($arr['uri']);
426 if (x($arr, 'gravity'))
427 $arr['gravity'] = intval($arr['gravity']);
428 elseif ($arr['parent-uri'] === $arr['uri'])
430 elseif (activity_match($arr['verb'],ACTIVITY_POST))
433 $arr['gravity'] = 6; // extensible catchall
435 if (! x($arr,'type'))
436 $arr['type'] = 'remote';
440 /* check for create date and expire time */
441 $uid = intval($arr['uid']);
442 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
444 $expire_interval = $r[0]['expire'];
445 if ($expire_interval>0) {
446 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
447 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
448 if ($created_date < $expire_date) {
449 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
455 // Do we already have this item?
456 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
457 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
458 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
459 dbesc(trim($arr['uri'])),
461 dbesc(NETWORK_DIASPORA),
463 dbesc(NETWORK_OSTATUS)
466 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
468 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']);
473 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
474 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
475 //if ((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
476 // $arr['body'] = strip_tags($arr['body']);
478 item_add_language_opt($arr);
482 elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
483 $arr['guid'] = uri_to_guid($arr['plink']);
484 elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
485 $arr['guid'] = uri_to_guid($arr['uri']);
487 $parsed = parse_url($arr["author-link"]);
488 $guid_prefix = hash("crc32", $parsed["host"]);
491 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
492 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
493 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : item_new_uri($a->get_hostname(), $uid, $arr['guid']));
494 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
495 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
496 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
497 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
498 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
499 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
500 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
501 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
502 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
503 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
504 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
505 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
506 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
507 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
508 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
509 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
510 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
512 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : $arr['uri']);
513 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
514 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
515 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
516 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
517 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
518 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
519 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
520 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
521 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
522 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
523 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
524 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
525 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
526 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
527 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
528 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
529 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
530 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
531 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
532 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
533 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
534 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
535 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
537 // Items cannot be stored before they happen ...
538 if ($arr['created'] > datetime_convert())
539 $arr['created'] = datetime_convert();
541 // We haven't invented time travel by now.
542 if ($arr['edited'] > datetime_convert())
543 $arr['edited'] = datetime_convert();
545 if (($arr['author-link'] == "") AND ($arr['owner-link'] == ""))
546 logger("Both author-link and owner-link are empty. Called by: ".App::callstack(), LOGGER_DEBUG);
548 if ($arr['plink'] == "") {
550 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
553 if ($arr['network'] == "") {
554 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
555 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
556 dbesc(normalise_link($arr['author-link'])),
561 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
562 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
563 dbesc(normalise_link($arr['author-link']))
567 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
568 intval($arr['contact-id']),
573 $arr['network'] = $r[0]["network"];
575 // Fallback to friendica (why is it empty in some cases?)
576 if ($arr['network'] == "")
577 $arr['network'] = NETWORK_DFRN;
579 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
582 // The contact-id should be set before "item_store" was called - but there seems to be some issues
583 if ($arr["contact-id"] == 0) {
584 // First we are looking for a suitable contact that matches with the author of the post
585 // This is done only for comments (See below explanation at "gcontact-id")
586 if ($arr['parent-uri'] != $arr['uri'])
587 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
589 // If not present then maybe the owner was found
590 if ($arr["contact-id"] == 0)
591 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
593 // Still missing? Then use the "self" contact of the current user
594 if ($arr["contact-id"] == 0) {
595 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
597 $arr["contact-id"] = $r[0]["id"];
599 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
602 if ($arr["gcontact-id"] == 0) {
603 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
604 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
605 // On comments the author is the better choice.
606 if ($arr['parent-uri'] === $arr['uri'])
607 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
608 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
610 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
611 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
614 if ($arr["author-id"] == 0)
615 $arr["author-id"] = get_contact($arr["author-link"], 0);
617 if ($arr["owner-id"] == 0)
618 $arr["owner-id"] = get_contact($arr["owner-link"], 0);
620 if ($arr['guid'] != "") {
621 // Checking if there is already an item with the same guid
622 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
623 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
624 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
627 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
632 // Check for hashtags in the body and repair or add hashtag links
633 item_body_set_hashtags($arr);
635 $arr['thr-parent'] = $arr['parent-uri'];
636 if ($arr['parent-uri'] === $arr['uri']) {
639 $allow_cid = $arr['allow_cid'];
640 $allow_gid = $arr['allow_gid'];
641 $deny_cid = $arr['deny_cid'];
642 $deny_gid = $arr['deny_gid'];
643 $notify_type = 'wall-new';
646 // find the parent and snarf the item id and ACLs
647 // and anything else we need to inherit
649 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
650 dbesc($arr['parent-uri']),
656 // is the new message multi-level threaded?
657 // even though we don't support it now, preserve the info
658 // and re-attach to the conversation parent.
660 if ($r[0]['uri'] != $r[0]['parent-uri']) {
661 $arr['parent-uri'] = $r[0]['parent-uri'];
662 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
663 ORDER BY `id` ASC LIMIT 1",
664 dbesc($r[0]['parent-uri']),
665 dbesc($r[0]['parent-uri']),
672 $parent_id = $r[0]['id'];
673 $parent_deleted = $r[0]['deleted'];
674 $allow_cid = $r[0]['allow_cid'];
675 $allow_gid = $r[0]['allow_gid'];
676 $deny_cid = $r[0]['deny_cid'];
677 $deny_gid = $r[0]['deny_gid'];
678 $arr['wall'] = $r[0]['wall'];
679 $notify_type = 'comment-new';
681 // if the parent is private, force privacy for the entire conversation
682 // This differs from the above settings as it subtly allows comments from
683 // email correspondents to be private even if the overall thread is not.
685 if ($r[0]['private'])
686 $arr['private'] = $r[0]['private'];
688 // Edge case. We host a public forum that was originally posted to privately.
689 // The original author commented, but as this is a comment, the permissions
690 // weren't fixed up so it will still show the comment as private unless we fix it here.
692 if ((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
696 // If its a post from myself then tag the thread as "mention"
697 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
698 $u = q("SELECT `nickname` FROM `user` WHERE `uid` = %d", intval($arr['uid']));
701 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
702 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
703 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
704 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
705 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
710 // Allow one to see reply tweets from status.net even when
711 // we don't have or can't see the original post.
714 logger('item_store: $force_parent=true, reply converted to top-level post.');
716 $arr['parent-uri'] = $arr['uri'];
719 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
727 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
729 dbesc($arr['network']),
733 if (dbm::is_result($r)) {
734 logger('duplicated item with the same uri found. '.print_r($arr,true));
738 // On Friendica and Diaspora the GUID is unique
739 if (in_array($arr['network'], array(NETWORK_DFRN, NETWORK_DIASPORA))) {
740 $r = q("SELECT `id` FROM `item` WHERE `guid` = '%s' AND `uid` = %d LIMIT 1",
744 if (dbm::is_result($r)) {
745 logger('duplicated item with the same guid found. '.print_r($arr,true));
749 // Check for an existing post with the same content. There seems to be a problem with OStatus.
750 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
752 dbesc($arr['network']),
753 dbesc($arr['created']),
754 intval($arr['contact-id']),
757 if (dbm::is_result($r)) {
758 logger('duplicated item with the same body found. '.print_r($arr,true));
763 // Is this item available in the global items (with uid=0)?
764 if ($arr["uid"] == 0) {
765 $arr["global"] = true;
767 // Set the global flag on all items if this was a global item entry
768 q("UPDATE `item` SET `global` = 1 WHERE `uri` = '%s'", dbesc($arr["uri"]));
770 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `uri` = '%s'", dbesc($arr["uri"]));
772 $arr["global"] = (count($isglobal) > 0);
776 if (strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
779 $private = $arr['private'];
781 $arr["allow_cid"] = $allow_cid;
782 $arr["allow_gid"] = $allow_gid;
783 $arr["deny_cid"] = $deny_cid;
784 $arr["deny_gid"] = $deny_gid;
785 $arr["private"] = $private;
786 $arr["deleted"] = $parent_deleted;
788 // Fill the cache field
789 put_item_in_cache($arr);
792 call_hooks('post_local',$arr);
794 call_hooks('post_remote',$arr);
796 if (x($arr,'cancel')) {
797 logger('item_store: post cancelled by plugin.');
801 // Check for already added items.
802 // There is a timing issue here that sometimes creates double postings.
803 // An unique index would help - but the limitations of MySQL (maximum size of index values) prevent this.
804 if ($arr["uid"] == 0) {
805 $r = qu("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = 0 LIMIT 1", dbesc(trim($arr['uri'])));
806 if (dbm::is_result($r)) {
807 logger('Global item already stored. URI: '.$arr['uri'].' on network '.$arr['network'], LOGGER_DEBUG);
812 // Store the unescaped version
817 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
820 q("START TRANSACTION;");
822 $r = dbq("INSERT INTO `item` (`"
823 . implode("`, `", array_keys($arr))
825 . implode("', '", array_values($arr))
831 // When the item was successfully stored we fetch the ID of the item.
832 if (dbm::is_result($r)) {
833 $r = q("SELECT LAST_INSERT_ID() AS `item-id`");
834 if (dbm::is_result($r)) {
835 $current_post = $r[0]['item-id'];
837 // This shouldn't happen
841 // This can happen - for example - if there are locking timeouts.
842 logger("Item wasn't stored - we quit here.");
847 if ($current_post == 0) {
848 // This is one of these error messages that never should occur.
849 logger("couldn't find created item - we better quit now.");
854 // How much entries have we created?
855 // We wouldn't need this query when we could use an unique index - but MySQL has length problems with them.
856 $r = q("SELECT COUNT(*) AS `entries` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s'",
859 dbesc($arr['network'])
862 if (!dbm::is_result($r)) {
863 // This shouldn't happen, since COUNT always works when the database connection is there.
864 logger("We couldn't count the stored entries. Very strange ...");
869 if ($r[0]["entries"] > 1) {
870 // There are duplicates. We delete our just created entry.
871 logger('Duplicated post occurred. uri = '.$arr['uri'].' uid = '.$arr['uid']);
873 // Yes, we could do a rollback here - but we are having many users with MyISAM.
874 q("DELETE FROM `item` WHERE `id` = %d", intval($current_post));
877 } elseif ($r[0]["entries"] == 0) {
878 // This really should never happen since we quit earlier if there were problems.
879 logger("Something is terribly wrong. We haven't found our created entry.");
884 logger('item_store: created item '.$current_post);
885 item_set_last_item($arr);
887 if (!$parent_id || ($arr['parent-uri'] === $arr['uri']))
888 $parent_id = $current_post;
891 $r = q("UPDATE `item` SET `parent` = %d WHERE `id` = %d",
893 intval($current_post)
896 $arr['id'] = $current_post;
897 $arr['parent'] = $parent_id;
899 // update the commented timestamp on the parent
900 // Only update "commented" if it is really a comment
901 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
902 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
903 dbesc(datetime_convert()),
904 dbesc(datetime_convert()),
908 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
909 dbesc(datetime_convert()),
915 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
916 // We can check for this condition when we decode and encode the stuff again.
917 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
918 $dsprsig->signature = base64_decode($dsprsig->signature);
919 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
922 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
923 intval($current_post),
924 dbesc($dsprsig->signed_text),
925 dbesc($dsprsig->signature),
926 dbesc($dsprsig->signer)
930 $deleted = tag_deliver($arr['uid'],$current_post);
932 // current post can be deleted if is for a community page and no mention are
934 if (!$deleted AND !$dontcache) {
936 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
937 if (count($r) == 1) {
939 call_hooks('post_local_end', $r[0]);
941 call_hooks('post_remote_end', $r[0]);
943 logger('item_store: new item not found in DB, id ' . $current_post);
946 if ($arr['parent-uri'] === $arr['uri']) {
947 add_thread($current_post);
949 update_thread($parent_id);
954 // Due to deadlock issues with the "term" table we are doing these steps after the commit.
955 // This is not perfect - but a workable solution until we found the reason for the problem.
956 create_tags_from_item($current_post);
957 create_files_from_item($current_post);
959 // If this is now the last-child, force all _other_ children of this parent to *not* be last-child
960 // It is done after the transaction to avoid dead locks.
961 if ($arr['last-child']) {
962 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
965 intval($current_post)
969 if ($arr['parent-uri'] === $arr['uri']) {
970 add_shadow_thread($current_post);
972 add_shadow_entry($current_post);
975 check_item_notification($current_post, $uid);
978 proc_run(PRIORITY_HIGH, "include/notifier.php", $notify_type, $current_post);
980 return $current_post;
984 * @brief Set "success_update" and "last-item" to the date of the last time we heard from this contact
986 * This can be used to filter for inactive contacts.
987 * Only do this for public postings to avoid privacy problems, since poco data is public.
988 * Don't set this value if it isn't from the owner (could be an author that we don't know)
990 * @param array $arr Contains the just posted item record
992 function item_set_last_item($arr) {
994 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
996 // Is it a forum? Then we don't care about the rules from above
997 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
998 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
999 intval($arr['contact-id']));
1006 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1007 dbesc($arr['received']),
1008 dbesc($arr['received']),
1009 intval($arr['contact-id'])
1012 // Now do the same for the system wide contacts with uid=0
1013 if (!$arr['private']) {
1014 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1015 dbesc($arr['received']),
1016 dbesc($arr['received']),
1017 intval($arr['owner-id'])
1020 if ($arr['owner-id'] != $arr['author-id']) {
1021 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1022 dbesc($arr['received']),
1023 dbesc($arr['received']),
1024 intval($arr['author-id'])
1030 function item_body_set_hashtags(&$item) {
1032 $tags = get_tags($item["body"]);
1038 // This sorting is important when there are hashtags that are part of other hashtags
1039 // Otherwise there could be problems with hashtags like #test and #test2
1044 $URLSearchString = "^\[\]";
1046 // All hashtags should point to the home server
1047 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1048 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1050 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1051 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1053 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1054 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1056 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1059 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1061 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1064 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1066 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1069 // Repair recursive urls
1070 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1071 "#$2", $item["body"]);
1074 foreach($tags as $tag) {
1075 if (strpos($tag,'#') !== 0)
1078 if (strpos($tag,'[url='))
1081 $basetag = str_replace('_',' ',substr($tag,1));
1083 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1085 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1087 if (!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1088 if (strlen($item["tag"]))
1089 $item["tag"] = ','.$item["tag"];
1090 $item["tag"] = $newtag.$item["tag"];
1094 // Convert back the masked hashtags
1095 $item["body"] = str_replace("#", "#", $item["body"]);
1098 function get_item_guid($id) {
1099 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1101 return($r[0]["guid"]);
1106 function get_item_id($guid, $uid = 0) {
1112 $uid == local_user();
1114 // Does the given user have this item?
1116 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1117 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1118 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1121 $nick = $r[0]["nickname"];
1125 // Or is it anywhere on the server?
1127 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1128 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1129 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1130 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1131 AND `item`.`private` = 0 AND `item`.`wall` = 1
1132 AND `item`.`guid` = '%s'", dbesc($guid));
1135 $nick = $r[0]["nickname"];
1138 return(array("nick" => $nick, "id" => $id));
1142 function get_item_contact($item,$contacts) {
1143 if (! count($contacts) || (! is_array($item)))
1145 foreach($contacts as $contact) {
1146 if ($contact['id'] == $item['contact-id']) {
1148 break; // NOTREACHED
1155 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1157 * @param int $item_id
1158 * @return bool true if item was deleted, else false
1160 function tag_deliver($uid,$item_id) {
1168 $u = q("select * from user where uid = %d limit 1",
1174 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1175 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1178 $i = q("select * from item where id = %d and uid = %d limit 1",
1187 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1189 // Diaspora uses their own hardwired link URL in @-tags
1190 // instead of the one we supply with webfinger
1192 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1194 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1196 foreach($matches as $mtch) {
1197 if (link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1199 logger('tag_deliver: mention found: ' . $mtch[2]);
1205 if ( ($community_page || $prvgroup) &&
1206 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1207 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1209 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1210 q("DELETE FROM item WHERE id = %d and uid = %d",
1219 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1221 call_hooks('tagged', $arr);
1223 if ((! $community_page) && (! $prvgroup))
1227 // tgroup delivery - setup a second delivery chain
1228 // prevent delivery looping - only proceed
1229 // if the message originated elsewhere and is a top-level post
1231 if (($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1234 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1237 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1238 intval($u[0]['uid'])
1243 // also reset all the privacy bits to the forum default permissions
1245 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1247 $forum_mode = (($prvgroup) ? 2 : 1);
1249 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1250 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1251 intval($forum_mode),
1252 dbesc($c[0]['name']),
1253 dbesc($c[0]['url']),
1254 dbesc($c[0]['thumb']),
1256 dbesc($u[0]['allow_cid']),
1257 dbesc($u[0]['allow_gid']),
1258 dbesc($u[0]['deny_cid']),
1259 dbesc($u[0]['deny_gid']),
1262 update_thread($item_id);
1264 proc_run(PRIORITY_HIGH,'include/notifier.php', 'tgroup', $item_id);
1270 function tgroup_check($uid,$item) {
1276 // check that the message originated elsewhere and is a top-level post
1278 if (($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1282 $u = q("select * from user where uid = %d limit 1",
1288 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1289 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1292 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1294 // Diaspora uses their own hardwired link URL in @-tags
1295 // instead of the one we supply with webfinger
1297 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1299 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1301 foreach($matches as $mtch) {
1302 if (link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1304 logger('tgroup_check: mention found: ' . $mtch[2]);
1312 if ((! $community_page) && (! $prvgroup))
1319 This function returns true if $update has an edited timestamp newer
1320 than $existing, i.e. $update contains new data which should override
1321 what's already there. If there is no timestamp yet, the update is
1322 assumed to be newer. If the update has no timestamp, the existing
1323 item is assumed to be up-to-date. If the timestamps are equal it
1324 assumes the update has been seen before and should be ignored.
1326 function edited_timestamp_is_newer($existing, $update) {
1327 if (!x($existing,'edited') || !$existing['edited']) {
1330 if (!x($update,'edited') || !$update['edited']) {
1333 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1334 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1335 return (strcmp($existing_edited, $update_edited) < 0);
1340 * consume_feed - process atom feed and update anything/everything we might need to update
1342 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1344 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1345 * It is this person's stuff that is going to be updated.
1346 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1347 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1348 * have a contact record.
1349 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1350 * might not) try and subscribe to it.
1351 * $datedir sorts in reverse order
1352 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1353 * imported prior to its children being seen in the stream unless we are certain
1354 * of how the feed is arranged/ordered.
1355 * With $pass = 1, we only pull parent items out of the stream.
1356 * With $pass = 2, we only pull children (comments/likes).
1358 * So running this twice, first with pass 1 and then with pass 2 will do the right
1359 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1360 * model where comments can have sub-threads. That would require some massive sorting
1361 * to get all the feed items into a mostly linear ordering, and might still require
1365 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1366 if ($contact['network'] === NETWORK_OSTATUS) {
1368 // Test - remove before flight
1369 //$tempfile = tempnam(get_temppath(), "ostatus2");
1370 //file_put_contents($tempfile, $xml);
1371 logger("Consume OStatus messages ", LOGGER_DEBUG);
1372 ostatus::import($xml,$importer,$contact, $hub);
1377 if ($contact['network'] === NETWORK_FEED) {
1379 logger("Consume feeds", LOGGER_DEBUG);
1380 feed_import($xml,$importer,$contact, $hub);
1385 if ($contact['network'] === NETWORK_DFRN) {
1386 logger("Consume DFRN messages", LOGGER_DEBUG);
1388 $r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
1389 `contact`.`pubkey` AS `cpubkey`,
1390 `contact`.`prvkey` AS `cprvkey`,
1391 `contact`.`thumb` AS `thumb`,
1392 `contact`.`url` as `url`,
1393 `contact`.`name` as `senderName`,
1396 LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
1397 WHERE `contact`.`id` = %d AND `user`.`uid` = %d",
1398 dbesc($contact["id"]), dbesc($importer["uid"])
1401 logger("Now import the DFRN feed");
1402 dfrn::import($xml,$r[0], true);
1408 function item_is_remote_self($contact, &$datarray) {
1411 if (!$contact['remote_self'])
1414 // Prevent the forwarding of posts that are forwarded
1415 if ($datarray["extid"] == NETWORK_DFRN)
1418 // Prevent to forward already forwarded posts
1419 if ($datarray["app"] == $a->get_hostname())
1422 // Only forward posts
1423 if ($datarray["verb"] != ACTIVITY_POST)
1426 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
1429 $datarray2 = $datarray;
1430 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
1431 if ($contact['remote_self'] == 2) {
1432 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
1433 intval($contact['uid']));
1435 $datarray['contact-id'] = $r[0]["id"];
1437 $datarray['owner-name'] = $r[0]["name"];
1438 $datarray['owner-link'] = $r[0]["url"];
1439 $datarray['owner-avatar'] = $r[0]["thumb"];
1441 $datarray['author-name'] = $datarray['owner-name'];
1442 $datarray['author-link'] = $datarray['owner-link'];
1443 $datarray['author-avatar'] = $datarray['owner-avatar'];
1446 if ($contact['network'] != NETWORK_FEED) {
1447 $datarray["guid"] = get_guid(32);
1448 unset($datarray["plink"]);
1449 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
1450 $datarray["parent-uri"] = $datarray["uri"];
1451 $datarray["extid"] = $contact['network'];
1452 $urlpart = parse_url($datarray2['author-link']);
1453 $datarray["app"] = $urlpart["host"];
1455 $datarray['private'] = 0;
1458 if ($contact['network'] != NETWORK_FEED) {
1459 // Store the original post
1460 $r = item_store($datarray2, false, false);
1461 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
1463 $datarray["app"] = "Feed";
1468 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
1469 $url = notags(trim($datarray['author-link']));
1470 $name = notags(trim($datarray['author-name']));
1471 $photo = notags(trim($datarray['author-avatar']));
1473 if (is_object($item)) {
1474 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
1475 if ($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
1476 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
1480 if (is_array($contact)) {
1481 if (($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
1482 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
1483 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
1484 intval(CONTACT_IS_FRIEND),
1485 intval($contact['id']),
1486 intval($importer['uid'])
1489 // send email notification to owner?
1492 // create contact record
1494 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
1495 `blocked`, `readonly`, `pending`, `writable`)
1496 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
1497 intval($importer['uid']),
1498 dbesc(datetime_convert()),
1500 dbesc(normalise_link($url)),
1504 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
1505 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
1507 $r = q("SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
1508 intval($importer['uid']),
1512 $contact_record = $r[0];
1513 update_contact_avatar($photo, $importer["uid"], $contact_record["id"], true);
1517 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
1518 intval($importer['uid'])
1521 if (count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1523 // create notification
1524 $hash = random_string();
1526 if (is_array($contact_record)) {
1527 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
1528 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
1529 intval($importer['uid']),
1530 intval($contact_record['id']),
1532 dbesc(datetime_convert())
1536 $def_gid = get_default_group($importer['uid'], $contact_record["network"]);
1538 if (intval($def_gid))
1539 group_add_member($importer['uid'],'',$contact_record['id'],$def_gid);
1541 if (($r[0]['notify-flags'] & NOTIFY_INTRO) &&
1542 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
1545 'type' => NOTIFY_INTRO,
1546 'notify_flags' => $r[0]['notify-flags'],
1547 'language' => $r[0]['language'],
1548 'to_name' => $r[0]['username'],
1549 'to_email' => $r[0]['email'],
1550 'uid' => $r[0]['uid'],
1551 'link' => $a->get_baseurl() . '/notifications/intro',
1552 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
1553 'source_link' => $contact_record['url'],
1554 'source_photo' => $contact_record['photo'],
1555 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
1560 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1561 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
1562 intval($importer['uid']),
1570 function lose_follower($importer,$contact,$datarray = array(),$item = "") {
1572 if (($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
1573 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1574 intval(CONTACT_IS_SHARING),
1575 intval($contact['id'])
1578 contact_remove($contact['id']);
1582 function lose_sharer($importer,$contact,$datarray = array(),$item = "") {
1584 if (($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
1585 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1586 intval(CONTACT_IS_FOLLOWER),
1587 intval($contact['id'])
1590 contact_remove($contact['id']);
1594 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
1598 if (is_array($importer)) {
1599 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
1600 intval($importer['uid'])
1604 // Diaspora has different message-ids in feeds than they do
1605 // through the direct Diaspora protocol. If we try and use
1606 // the feed, we'll get duplicates. So don't.
1608 if ((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
1611 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
1613 // Use a single verify token, even if multiple hubs
1615 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
1617 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
1619 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
1621 if (!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
1622 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
1623 dbesc($verify_token),
1624 intval($contact['id'])
1628 post_url($url,$params);
1630 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
1636 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
1638 if (get_config('system','disable_embedded'))
1643 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
1644 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
1649 $img_start = strpos($orig_body, '[img');
1650 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1651 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1652 while( ($img_st_close !== false) && ($img_len !== false) ) {
1654 $img_st_close++; // make it point to AFTER the closing bracket
1655 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
1657 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
1660 if (stristr($image , $site . '/photo/')) {
1661 // Only embed locally hosted photos
1663 $i = basename($image);
1664 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
1665 $x = strpos($i,'-');
1668 $res = substr($i,$x+1);
1669 $i = substr($i,0,$x);
1670 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
1677 // Check to see if we should replace this photo link with an embedded image
1678 // 1. No need to do so if the photo is public
1679 // 2. If there's a contact-id provided, see if they're in the access list
1680 // for the photo. If so, embed it.
1681 // 3. Otherwise, if we have an item, see if the item permissions match the photo
1682 // permissions, regardless of order but first check to see if they're an exact
1683 // match to save some processing overhead.
1685 if (has_permissions($r[0])) {
1687 $recips = enumerate_permissions($r[0]);
1688 if (in_array($cid, $recips)) {
1692 if (compare_permissions($item,$r[0]))
1697 $data = $r[0]['data'];
1698 $type = $r[0]['type'];
1700 // If a custom width and height were specified, apply before embedding
1701 if (preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
1702 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
1704 $width = intval($match[1]);
1705 $height = intval($match[2]);
1707 $ph = new Photo($data, $type);
1708 if ($ph->is_valid()) {
1709 $ph->scaleImage(max($width, $height));
1710 $data = $ph->imageString();
1711 $type = $ph->getType();
1715 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
1716 $image = 'data:' . $type . ';base64,' . base64_encode($data);
1717 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
1723 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
1724 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
1725 if ($orig_body === false)
1728 $img_start = strpos($orig_body, '[img');
1729 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1730 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1733 $new_body = $new_body . $orig_body;
1738 function has_permissions($obj) {
1739 if (($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
1744 function compare_permissions($obj1,$obj2) {
1745 // first part is easy. Check that these are exactly the same.
1746 if (($obj1['allow_cid'] == $obj2['allow_cid'])
1747 && ($obj1['allow_gid'] == $obj2['allow_gid'])
1748 && ($obj1['deny_cid'] == $obj2['deny_cid'])
1749 && ($obj1['deny_gid'] == $obj2['deny_gid']))
1752 // This is harder. Parse all the permissions and compare the resulting set.
1754 $recipients1 = enumerate_permissions($obj1);
1755 $recipients2 = enumerate_permissions($obj2);
1758 if ($recipients1 == $recipients2)
1763 // returns an array of contact-ids that are allowed to see this object
1765 function enumerate_permissions($obj) {
1766 $allow_people = expand_acl($obj['allow_cid']);
1767 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
1768 $deny_people = expand_acl($obj['deny_cid']);
1769 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
1770 $recipients = array_unique(array_merge($allow_people,$allow_groups));
1771 $deny = array_unique(array_merge($deny_people,$deny_groups));
1772 $recipients = array_diff($recipients,$deny);
1776 function item_getfeedtags($item) {
1779 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1781 for($x = 0; $x < $cnt; $x ++) {
1782 if ($matches[1][$x])
1783 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
1787 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1789 for($x = 0; $x < $cnt; $x ++) {
1790 if ($matches[1][$x])
1791 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
1797 function item_expire($uid, $days, $network = "", $force = false) {
1799 if ((! $uid) || ($days < 1))
1802 // $expire_network_only = save your own wall posts
1803 // and just expire conversations started by others
1805 $expire_network_only = get_pconfig($uid,'expire','network_only');
1806 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
1808 if ($network != "") {
1809 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
1810 // There is an index "uid_network_received" but not "uid_network_created"
1811 // This avoids the creation of another index just for one purpose.
1812 // And it doesn't really matter wether to look at "received" or "created"
1813 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1815 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1817 $r = q("SELECT `file`, `resource-id`, `starred`, `type`, `id` FROM `item`
1818 WHERE `uid` = %d $range
1829 $expire_items = get_pconfig($uid, 'expire','items');
1830 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
1832 // Forcing expiring of items - but not notes and marked items
1834 $expire_items = true;
1836 $expire_notes = get_pconfig($uid, 'expire','notes');
1837 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
1839 $expire_starred = get_pconfig($uid, 'expire','starred');
1840 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
1842 $expire_photos = get_pconfig($uid, 'expire','photos');
1843 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
1845 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
1847 foreach($r as $item) {
1849 // don't expire filed items
1851 if (strpos($item['file'],'[') !== false)
1854 // Only expire posts, not photos and photo comments
1856 if ($expire_photos==0 && strlen($item['resource-id']))
1858 if ($expire_starred==0 && intval($item['starred']))
1860 if ($expire_notes==0 && $item['type']=='note')
1862 if ($expire_items==0 && $item['type']!='note')
1865 drop_item($item['id'],false);
1868 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1873 function drop_items($items) {
1876 if (! local_user() && ! remote_user())
1879 if (count($items)) {
1880 foreach($items as $item) {
1881 $owner = drop_item($item,false);
1882 if ($owner && ! $uid)
1887 // multiple threads may have been deleted, send an expire notification
1890 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1894 function drop_item($id,$interactive = true) {
1898 // locate item to be deleted
1900 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
1907 notice( t('Item not found.') . EOL);
1908 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1913 $owner = $item['uid'];
1917 // check if logged in user is either the author or owner of this item
1919 if (is_array($_SESSION['remote'])) {
1920 foreach($_SESSION['remote'] as $visitor) {
1921 if ($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
1922 $cid = $visitor['cid'];
1929 if ((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
1931 // Check if we should do HTML-based delete confirmation
1932 if ($_REQUEST['confirm']) {
1933 // <form> can't take arguments in its "action" parameter
1934 // so add any arguments as hidden inputs
1935 $query = explode_querystring($a->query_string);
1937 foreach($query['args'] as $arg) {
1938 if (strpos($arg, 'confirm=') === false) {
1939 $arg_parts = explode('=', $arg);
1940 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
1944 return replace_macros(get_markup_template('confirm.tpl'), array(
1946 '$message' => t('Do you really want to delete this item?'),
1947 '$extra_inputs' => $inputs,
1948 '$confirm' => t('Yes'),
1949 '$confirm_url' => $query['base'],
1950 '$confirm_name' => 'confirmed',
1951 '$cancel' => t('Cancel'),
1954 // Now check how the user responded to the confirmation query
1955 if ($_REQUEST['canceled']) {
1956 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1959 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
1962 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
1963 dbesc(datetime_convert()),
1964 dbesc(datetime_convert()),
1967 create_tags_from_item($item['id']);
1968 create_files_from_item($item['id']);
1969 delete_thread($item['id'], $item['parent-uri']);
1971 // clean up categories and tags so they don't end up as orphans
1974 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1976 foreach($matches as $mtch) {
1977 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
1983 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1985 foreach($matches as $mtch) {
1986 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
1990 // If item is a link to a photo resource, nuke all the associated photos
1991 // (visitors will not have photo resources)
1992 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
1993 // generate a resource-id and therefore aren't intimately linked to the item.
1995 if (strlen($item['resource-id'])) {
1996 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
1997 dbesc($item['resource-id']),
1998 intval($item['uid'])
2000 // ignore the result
2003 // If item is a link to an event, nuke the event record.
2005 if (intval($item['event-id'])) {
2006 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
2007 intval($item['event-id']),
2008 intval($item['uid'])
2010 // ignore the result
2013 // If item has attachments, drop them
2015 foreach(explode(",",$item['attach']) as $attach){
2016 preg_match("|attach/(\d+)|", $attach, $matches);
2017 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
2018 intval($matches[1]),
2021 // ignore the result
2025 // clean up item_id and sign meta-data tables
2028 // Old code - caused very long queries and warning entries in the mysql logfiles:
2030 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
2031 intval($item['id']),
2032 intval($item['uid'])
2035 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
2036 intval($item['id']),
2037 intval($item['uid'])
2041 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
2043 // Creating list of parents
2044 $r = q("select id from item where parent = %d and uid = %d",
2045 intval($item['id']),
2046 intval($item['uid'])
2051 foreach ($r AS $row) {
2052 if ($parentid != "")
2055 $parentid .= $row["id"];
2059 if ($parentid != "") {
2060 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
2062 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
2065 // If it's the parent of a comment thread, kill all the kids
2067 if ($item['uri'] == $item['parent-uri']) {
2068 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
2069 WHERE `parent-uri` = '%s' AND `uid` = %d ",
2070 dbesc(datetime_convert()),
2071 dbesc(datetime_convert()),
2072 dbesc($item['parent-uri']),
2073 intval($item['uid'])
2075 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
2076 create_files_from_itemuri($item['parent-uri'], $item['uid']);
2077 delete_thread_uri($item['parent-uri'], $item['uid']);
2078 // ignore the result
2080 // ensure that last-child is set in case the comment that had it just got wiped.
2081 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2082 dbesc(datetime_convert()),
2083 dbesc($item['parent-uri']),
2084 intval($item['uid'])
2086 // who is the last child now?
2087 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d ORDER BY `edited` DESC LIMIT 1",
2088 dbesc($item['parent-uri']),
2089 intval($item['uid'])
2092 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2098 $drop_id = intval($item['id']);
2100 // send the notification upstream/downstream as the case may be
2102 proc_run(PRIORITY_HIGH,"include/notifier.php", "drop", $drop_id);
2106 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2111 notice( t('Permission denied.') . EOL);
2112 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2119 function first_post_date($uid,$wall = false) {
2120 $r = q("select id, created from item
2121 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
2123 order by created asc limit 1",
2125 intval($wall ? 1 : 0)
2128 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
2129 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
2134 /* modified posted_dates() {below} to arrange the list in years */
2135 function list_post_dates($uid, $wall) {
2136 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2138 $dthen = first_post_date($uid, $wall);
2142 // Set the start and end date to the beginning of the month
2143 $dnow = substr($dnow,0,8).'01';
2144 $dthen = substr($dthen,0,8).'01';
2148 // Starting with the current month, get the first and last days of every
2149 // month down to and including the month of the first post
2150 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2151 $dyear = intval(substr($dnow,0,4));
2152 $dstart = substr($dnow,0,8) . '01';
2153 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2154 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2155 $end_month = datetime_convert('','',$dend,'Y-m-d');
2156 $str = day_translate(datetime_convert('','',$dnow,'F'));
2158 $ret[$dyear] = array();
2159 $ret[$dyear][] = array($str,$end_month,$start_month);
2160 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2165 function posted_dates($uid,$wall) {
2166 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2168 $dthen = first_post_date($uid,$wall);
2172 // Set the start and end date to the beginning of the month
2173 $dnow = substr($dnow,0,8).'01';
2174 $dthen = substr($dthen,0,8).'01';
2177 // Starting with the current month, get the first and last days of every
2178 // month down to and including the month of the first post
2179 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2180 $dstart = substr($dnow,0,8) . '01';
2181 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2182 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2183 $end_month = datetime_convert('','',$dend,'Y-m-d');
2184 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
2185 $ret[] = array($str,$end_month,$start_month);
2186 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2192 function posted_date_widget($url,$uid,$wall) {
2195 if (! feature_enabled($uid,'archives'))
2198 // For former Facebook folks that left because of "timeline"
2200 /* if ($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
2203 $visible_years = get_pconfig($uid,'system','archive_visible_years');
2204 if (! $visible_years)
2207 $ret = list_post_dates($uid,$wall);
2212 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
2213 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
2215 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
2216 '$title' => t('Archives'),
2217 '$size' => $visible_years,
2218 '$cutoff_year' => $cutoff_year,
2219 '$cutoff' => $cutoff,
2222 '$showmore' => t('show more')