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 * @param string $host (Optional) hostname for the GUID prefix
363 * @return string unique guid
365 function uri_to_guid($uri, $host = "") {
367 // Our regular guid routine is using this kind of prefix as well
368 // We have to avoid that different routines could accidentally create the same value
369 $parsed = parse_url($uri);
372 $host = $parsed["host"];
375 $guid_prefix = hash("crc32", $host);
377 // Remove the scheme to make sure that "https" and "http" doesn't make a difference
378 unset($parsed["scheme"]);
380 $host_id = implode("/", $parsed);
382 // We could use any hash algorithm since it isn't a security issue
383 $host_hash = hash("ripemd128", $host_id);
385 return $guid_prefix.$host_hash;
388 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
392 // If it is a posting where users should get notifications, then define it as wall posting
395 $arr['type'] = 'wall';
397 $arr['last-child'] = 1;
398 $arr['network'] = NETWORK_DFRN;
400 // We have to avoid duplicates. So we create the GUID in form of a hash of the plink or uri.
401 // In difference to the call to "uri_to_guid" several lines below we add the hash of our own host.
402 // This is done because our host is the original creator of the post.
403 if (isset($arr['plink'])) {
404 $arr['guid'] = uri_to_guid($arr['plink'], $a->get_hostname());
405 } elseif (isset($arr['uri'])) {
406 $arr['guid'] = uri_to_guid($arr['uri'], $a->get_hostname());
410 // If a Diaspora signature structure was passed in, pull it out of the
411 // item array and set it aside for later storage.
414 if (x($arr,'dsprsig')) {
415 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
416 unset($arr['dsprsig']);
419 // Converting the plink
420 if ($arr['network'] == NETWORK_OSTATUS) {
421 if (isset($arr['plink']))
422 $arr['plink'] = ostatus::convert_href($arr['plink']);
423 elseif (isset($arr['uri']))
424 $arr['plink'] = ostatus::convert_href($arr['uri']);
427 if (x($arr, 'gravity'))
428 $arr['gravity'] = intval($arr['gravity']);
429 elseif ($arr['parent-uri'] === $arr['uri'])
431 elseif (activity_match($arr['verb'],ACTIVITY_POST))
434 $arr['gravity'] = 6; // extensible catchall
436 if (! x($arr,'type'))
437 $arr['type'] = 'remote';
441 /* check for create date and expire time */
442 $uid = intval($arr['uid']);
443 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
445 $expire_interval = $r[0]['expire'];
446 if ($expire_interval>0) {
447 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
448 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
449 if ($created_date < $expire_date) {
450 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
456 // Do we already have this item?
457 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
458 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
459 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
460 dbesc(trim($arr['uri'])),
462 dbesc(NETWORK_DIASPORA),
464 dbesc(NETWORK_OSTATUS)
467 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
469 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']);
474 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
475 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
476 //if ((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
477 // $arr['body'] = strip_tags($arr['body']);
479 item_add_language_opt($arr);
483 elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
484 $arr['guid'] = uri_to_guid($arr['plink']);
485 elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
486 $arr['guid'] = uri_to_guid($arr['uri']);
488 $parsed = parse_url($arr["author-link"]);
489 $guid_prefix = hash("crc32", $parsed["host"]);
492 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
493 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
494 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : item_new_uri($a->get_hostname(), $uid, $arr['guid']));
495 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
496 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
497 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
498 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
499 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
500 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
501 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
502 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
503 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
504 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
505 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
506 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
507 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
508 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
509 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
510 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
511 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
513 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : $arr['uri']);
514 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
515 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
516 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
517 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
518 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
519 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
520 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
521 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
522 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
523 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
524 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
525 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
526 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
527 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
528 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
529 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
530 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
531 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
532 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
533 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
534 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
535 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
536 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
538 // Items cannot be stored before they happen ...
539 if ($arr['created'] > datetime_convert())
540 $arr['created'] = datetime_convert();
542 // We haven't invented time travel by now.
543 if ($arr['edited'] > datetime_convert())
544 $arr['edited'] = datetime_convert();
546 if (($arr['author-link'] == "") AND ($arr['owner-link'] == ""))
547 logger("Both author-link and owner-link are empty. Called by: ".App::callstack(), LOGGER_DEBUG);
549 if ($arr['plink'] == "") {
551 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
554 if ($arr['network'] == "") {
555 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
556 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
557 dbesc(normalise_link($arr['author-link'])),
562 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
563 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
564 dbesc(normalise_link($arr['author-link']))
568 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
569 intval($arr['contact-id']),
574 $arr['network'] = $r[0]["network"];
576 // Fallback to friendica (why is it empty in some cases?)
577 if ($arr['network'] == "")
578 $arr['network'] = NETWORK_DFRN;
580 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
583 // The contact-id should be set before "item_store" was called - but there seems to be some issues
584 if ($arr["contact-id"] == 0) {
585 // First we are looking for a suitable contact that matches with the author of the post
586 // This is done only for comments (See below explanation at "gcontact-id")
587 if ($arr['parent-uri'] != $arr['uri'])
588 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
590 // If not present then maybe the owner was found
591 if ($arr["contact-id"] == 0)
592 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
594 // Still missing? Then use the "self" contact of the current user
595 if ($arr["contact-id"] == 0) {
596 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
598 $arr["contact-id"] = $r[0]["id"];
600 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
603 if ($arr["gcontact-id"] == 0) {
604 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
605 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
606 // On comments the author is the better choice.
607 if ($arr['parent-uri'] === $arr['uri'])
608 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
609 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
611 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
612 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
615 if ($arr["author-id"] == 0)
616 $arr["author-id"] = get_contact($arr["author-link"], 0);
618 if ($arr["owner-id"] == 0)
619 $arr["owner-id"] = get_contact($arr["owner-link"], 0);
621 if ($arr['guid'] != "") {
622 // Checking if there is already an item with the same guid
623 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
624 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
625 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
628 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
633 // Check for hashtags in the body and repair or add hashtag links
634 item_body_set_hashtags($arr);
636 $arr['thr-parent'] = $arr['parent-uri'];
637 if ($arr['parent-uri'] === $arr['uri']) {
640 $allow_cid = $arr['allow_cid'];
641 $allow_gid = $arr['allow_gid'];
642 $deny_cid = $arr['deny_cid'];
643 $deny_gid = $arr['deny_gid'];
644 $notify_type = 'wall-new';
647 // find the parent and snarf the item id and ACLs
648 // and anything else we need to inherit
650 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
651 dbesc($arr['parent-uri']),
657 // is the new message multi-level threaded?
658 // even though we don't support it now, preserve the info
659 // and re-attach to the conversation parent.
661 if ($r[0]['uri'] != $r[0]['parent-uri']) {
662 $arr['parent-uri'] = $r[0]['parent-uri'];
663 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
664 ORDER BY `id` ASC LIMIT 1",
665 dbesc($r[0]['parent-uri']),
666 dbesc($r[0]['parent-uri']),
673 $parent_id = $r[0]['id'];
674 $parent_deleted = $r[0]['deleted'];
675 $allow_cid = $r[0]['allow_cid'];
676 $allow_gid = $r[0]['allow_gid'];
677 $deny_cid = $r[0]['deny_cid'];
678 $deny_gid = $r[0]['deny_gid'];
679 $arr['wall'] = $r[0]['wall'];
680 $notify_type = 'comment-new';
682 // if the parent is private, force privacy for the entire conversation
683 // This differs from the above settings as it subtly allows comments from
684 // email correspondents to be private even if the overall thread is not.
686 if ($r[0]['private'])
687 $arr['private'] = $r[0]['private'];
689 // Edge case. We host a public forum that was originally posted to privately.
690 // The original author commented, but as this is a comment, the permissions
691 // weren't fixed up so it will still show the comment as private unless we fix it here.
693 if ((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
697 // If its a post from myself then tag the thread as "mention"
698 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
699 $u = q("SELECT `nickname` FROM `user` WHERE `uid` = %d", intval($arr['uid']));
702 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
703 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
704 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
705 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
706 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
711 // Allow one to see reply tweets from status.net even when
712 // we don't have or can't see the original post.
715 logger('item_store: $force_parent=true, reply converted to top-level post.');
717 $arr['parent-uri'] = $arr['uri'];
720 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
728 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
730 dbesc($arr['network']),
734 if (dbm::is_result($r)) {
735 logger('duplicated item with the same uri found. '.print_r($arr,true));
739 // On Friendica and Diaspora the GUID is unique
740 if (in_array($arr['network'], array(NETWORK_DFRN, NETWORK_DIASPORA))) {
741 $r = q("SELECT `id` FROM `item` WHERE `guid` = '%s' AND `uid` = %d LIMIT 1",
745 if (dbm::is_result($r)) {
746 logger('duplicated item with the same guid found. '.print_r($arr,true));
750 // Check for an existing post with the same content. There seems to be a problem with OStatus.
751 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
753 dbesc($arr['network']),
754 dbesc($arr['created']),
755 intval($arr['contact-id']),
758 if (dbm::is_result($r)) {
759 logger('duplicated item with the same body found. '.print_r($arr,true));
764 // Is this item available in the global items (with uid=0)?
765 if ($arr["uid"] == 0) {
766 $arr["global"] = true;
768 // Set the global flag on all items if this was a global item entry
769 q("UPDATE `item` SET `global` = 1 WHERE `uri` = '%s'", dbesc($arr["uri"]));
771 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `uri` = '%s'", dbesc($arr["uri"]));
773 $arr["global"] = (count($isglobal) > 0);
777 if (strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
780 $private = $arr['private'];
782 $arr["allow_cid"] = $allow_cid;
783 $arr["allow_gid"] = $allow_gid;
784 $arr["deny_cid"] = $deny_cid;
785 $arr["deny_gid"] = $deny_gid;
786 $arr["private"] = $private;
787 $arr["deleted"] = $parent_deleted;
789 // Fill the cache field
790 put_item_in_cache($arr);
793 call_hooks('post_local',$arr);
795 call_hooks('post_remote',$arr);
797 if (x($arr,'cancel')) {
798 logger('item_store: post cancelled by plugin.');
802 // Check for already added items.
803 // There is a timing issue here that sometimes creates double postings.
804 // An unique index would help - but the limitations of MySQL (maximum size of index values) prevent this.
805 if ($arr["uid"] == 0) {
806 $r = qu("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = 0 LIMIT 1", dbesc(trim($arr['uri'])));
807 if (dbm::is_result($r)) {
808 logger('Global item already stored. URI: '.$arr['uri'].' on network '.$arr['network'], LOGGER_DEBUG);
813 // Store the unescaped version
818 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
821 q("START TRANSACTION;");
823 $r = dbq("INSERT INTO `item` (`"
824 . implode("`, `", array_keys($arr))
826 . implode("', '", array_values($arr))
832 // When the item was successfully stored we fetch the ID of the item.
833 if (dbm::is_result($r)) {
834 $r = q("SELECT LAST_INSERT_ID() AS `item-id`");
835 if (dbm::is_result($r)) {
836 $current_post = $r[0]['item-id'];
838 // This shouldn't happen
842 // This can happen - for example - if there are locking timeouts.
843 logger("Item wasn't stored - we quit here.");
848 if ($current_post == 0) {
849 // This is one of these error messages that never should occur.
850 logger("couldn't find created item - we better quit now.");
855 // How much entries have we created?
856 // We wouldn't need this query when we could use an unique index - but MySQL has length problems with them.
857 $r = q("SELECT COUNT(*) AS `entries` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s'",
860 dbesc($arr['network'])
863 if (!dbm::is_result($r)) {
864 // This shouldn't happen, since COUNT always works when the database connection is there.
865 logger("We couldn't count the stored entries. Very strange ...");
870 if ($r[0]["entries"] > 1) {
871 // There are duplicates. We delete our just created entry.
872 logger('Duplicated post occurred. uri = '.$arr['uri'].' uid = '.$arr['uid']);
874 // Yes, we could do a rollback here - but we are having many users with MyISAM.
875 q("DELETE FROM `item` WHERE `id` = %d", intval($current_post));
878 } elseif ($r[0]["entries"] == 0) {
879 // This really should never happen since we quit earlier if there were problems.
880 logger("Something is terribly wrong. We haven't found our created entry.");
885 logger('item_store: created item '.$current_post);
886 item_set_last_item($arr);
888 if (!$parent_id || ($arr['parent-uri'] === $arr['uri']))
889 $parent_id = $current_post;
892 $r = q("UPDATE `item` SET `parent` = %d WHERE `id` = %d",
894 intval($current_post)
897 $arr['id'] = $current_post;
898 $arr['parent'] = $parent_id;
900 // update the commented timestamp on the parent
901 // Only update "commented" if it is really a comment
902 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
903 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
904 dbesc(datetime_convert()),
905 dbesc(datetime_convert()),
909 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
910 dbesc(datetime_convert()),
916 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
917 // We can check for this condition when we decode and encode the stuff again.
918 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
919 $dsprsig->signature = base64_decode($dsprsig->signature);
920 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
923 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
924 intval($current_post),
925 dbesc($dsprsig->signed_text),
926 dbesc($dsprsig->signature),
927 dbesc($dsprsig->signer)
931 $deleted = tag_deliver($arr['uid'],$current_post);
933 // current post can be deleted if is for a community page and no mention are
935 if (!$deleted AND !$dontcache) {
937 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
938 if (count($r) == 1) {
940 call_hooks('post_local_end', $r[0]);
942 call_hooks('post_remote_end', $r[0]);
944 logger('item_store: new item not found in DB, id ' . $current_post);
947 if ($arr['parent-uri'] === $arr['uri']) {
948 add_thread($current_post);
950 update_thread($parent_id);
955 // Due to deadlock issues with the "term" table we are doing these steps after the commit.
956 // This is not perfect - but a workable solution until we found the reason for the problem.
957 create_tags_from_item($current_post);
958 create_files_from_item($current_post);
960 // If this is now the last-child, force all _other_ children of this parent to *not* be last-child
961 // It is done after the transaction to avoid dead locks.
962 if ($arr['last-child']) {
963 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
966 intval($current_post)
970 if ($arr['parent-uri'] === $arr['uri']) {
971 add_shadow_thread($current_post);
973 add_shadow_entry($current_post);
976 check_item_notification($current_post, $uid);
979 proc_run(PRIORITY_HIGH, "include/notifier.php", $notify_type, $current_post);
981 return $current_post;
985 * @brief Set "success_update" and "last-item" to the date of the last time we heard from this contact
987 * This can be used to filter for inactive contacts.
988 * Only do this for public postings to avoid privacy problems, since poco data is public.
989 * Don't set this value if it isn't from the owner (could be an author that we don't know)
991 * @param array $arr Contains the just posted item record
993 function item_set_last_item($arr) {
995 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
997 // Is it a forum? Then we don't care about the rules from above
998 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
999 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1000 intval($arr['contact-id']));
1007 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1008 dbesc($arr['received']),
1009 dbesc($arr['received']),
1010 intval($arr['contact-id'])
1013 // Now do the same for the system wide contacts with uid=0
1014 if (!$arr['private']) {
1015 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1016 dbesc($arr['received']),
1017 dbesc($arr['received']),
1018 intval($arr['owner-id'])
1021 if ($arr['owner-id'] != $arr['author-id']) {
1022 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1023 dbesc($arr['received']),
1024 dbesc($arr['received']),
1025 intval($arr['author-id'])
1031 function item_body_set_hashtags(&$item) {
1033 $tags = get_tags($item["body"]);
1039 // This sorting is important when there are hashtags that are part of other hashtags
1040 // Otherwise there could be problems with hashtags like #test and #test2
1045 $URLSearchString = "^\[\]";
1047 // All hashtags should point to the home server
1048 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1049 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1051 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1052 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1054 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1055 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1057 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1060 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1062 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1065 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1067 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1070 // Repair recursive urls
1071 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1072 "#$2", $item["body"]);
1075 foreach($tags as $tag) {
1076 if (strpos($tag,'#') !== 0)
1079 if (strpos($tag,'[url='))
1082 $basetag = str_replace('_',' ',substr($tag,1));
1084 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1086 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1088 if (!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1089 if (strlen($item["tag"]))
1090 $item["tag"] = ','.$item["tag"];
1091 $item["tag"] = $newtag.$item["tag"];
1095 // Convert back the masked hashtags
1096 $item["body"] = str_replace("#", "#", $item["body"]);
1099 function get_item_guid($id) {
1100 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1102 return($r[0]["guid"]);
1107 function get_item_id($guid, $uid = 0) {
1113 $uid == local_user();
1115 // Does the given user have this item?
1117 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1118 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1119 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1122 $nick = $r[0]["nickname"];
1126 // Or is it anywhere on the server?
1128 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1129 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1130 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1131 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1132 AND `item`.`private` = 0 AND `item`.`wall` = 1
1133 AND `item`.`guid` = '%s'", dbesc($guid));
1136 $nick = $r[0]["nickname"];
1139 return(array("nick" => $nick, "id" => $id));
1143 function get_item_contact($item,$contacts) {
1144 if (! count($contacts) || (! is_array($item)))
1146 foreach($contacts as $contact) {
1147 if ($contact['id'] == $item['contact-id']) {
1149 break; // NOTREACHED
1156 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1158 * @param int $item_id
1159 * @return bool true if item was deleted, else false
1161 function tag_deliver($uid,$item_id) {
1169 $u = q("select * from user where uid = %d limit 1",
1175 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1176 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1179 $i = q("select * from item where id = %d and uid = %d limit 1",
1188 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1190 // Diaspora uses their own hardwired link URL in @-tags
1191 // instead of the one we supply with webfinger
1193 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1195 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1197 foreach($matches as $mtch) {
1198 if (link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1200 logger('tag_deliver: mention found: ' . $mtch[2]);
1206 if ( ($community_page || $prvgroup) &&
1207 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1208 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1210 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1211 q("DELETE FROM item WHERE id = %d and uid = %d",
1220 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1222 call_hooks('tagged', $arr);
1224 if ((! $community_page) && (! $prvgroup))
1228 // tgroup delivery - setup a second delivery chain
1229 // prevent delivery looping - only proceed
1230 // if the message originated elsewhere and is a top-level post
1232 if (($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1235 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1238 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1239 intval($u[0]['uid'])
1244 // also reset all the privacy bits to the forum default permissions
1246 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1248 $forum_mode = (($prvgroup) ? 2 : 1);
1250 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1251 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1252 intval($forum_mode),
1253 dbesc($c[0]['name']),
1254 dbesc($c[0]['url']),
1255 dbesc($c[0]['thumb']),
1257 dbesc($u[0]['allow_cid']),
1258 dbesc($u[0]['allow_gid']),
1259 dbesc($u[0]['deny_cid']),
1260 dbesc($u[0]['deny_gid']),
1263 update_thread($item_id);
1265 proc_run(PRIORITY_HIGH,'include/notifier.php', 'tgroup', $item_id);
1271 function tgroup_check($uid,$item) {
1277 // check that the message originated elsewhere and is a top-level post
1279 if (($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1283 $u = q("select * from user where uid = %d limit 1",
1289 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1290 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1293 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1295 // Diaspora uses their own hardwired link URL in @-tags
1296 // instead of the one we supply with webfinger
1298 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1300 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1302 foreach($matches as $mtch) {
1303 if (link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1305 logger('tgroup_check: mention found: ' . $mtch[2]);
1313 if ((! $community_page) && (! $prvgroup))
1320 This function returns true if $update has an edited timestamp newer
1321 than $existing, i.e. $update contains new data which should override
1322 what's already there. If there is no timestamp yet, the update is
1323 assumed to be newer. If the update has no timestamp, the existing
1324 item is assumed to be up-to-date. If the timestamps are equal it
1325 assumes the update has been seen before and should be ignored.
1327 function edited_timestamp_is_newer($existing, $update) {
1328 if (!x($existing,'edited') || !$existing['edited']) {
1331 if (!x($update,'edited') || !$update['edited']) {
1334 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1335 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1336 return (strcmp($existing_edited, $update_edited) < 0);
1341 * consume_feed - process atom feed and update anything/everything we might need to update
1343 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1345 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1346 * It is this person's stuff that is going to be updated.
1347 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1348 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1349 * have a contact record.
1350 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1351 * might not) try and subscribe to it.
1352 * $datedir sorts in reverse order
1353 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1354 * imported prior to its children being seen in the stream unless we are certain
1355 * of how the feed is arranged/ordered.
1356 * With $pass = 1, we only pull parent items out of the stream.
1357 * With $pass = 2, we only pull children (comments/likes).
1359 * So running this twice, first with pass 1 and then with pass 2 will do the right
1360 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1361 * model where comments can have sub-threads. That would require some massive sorting
1362 * to get all the feed items into a mostly linear ordering, and might still require
1366 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1367 if ($contact['network'] === NETWORK_OSTATUS) {
1369 // Test - remove before flight
1370 //$tempfile = tempnam(get_temppath(), "ostatus2");
1371 //file_put_contents($tempfile, $xml);
1372 logger("Consume OStatus messages ", LOGGER_DEBUG);
1373 ostatus::import($xml,$importer,$contact, $hub);
1378 if ($contact['network'] === NETWORK_FEED) {
1380 logger("Consume feeds", LOGGER_DEBUG);
1381 feed_import($xml,$importer,$contact, $hub);
1386 if ($contact['network'] === NETWORK_DFRN) {
1387 logger("Consume DFRN messages", LOGGER_DEBUG);
1389 $r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
1390 `contact`.`pubkey` AS `cpubkey`,
1391 `contact`.`prvkey` AS `cprvkey`,
1392 `contact`.`thumb` AS `thumb`,
1393 `contact`.`url` as `url`,
1394 `contact`.`name` as `senderName`,
1397 LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
1398 WHERE `contact`.`id` = %d AND `user`.`uid` = %d",
1399 dbesc($contact["id"]), dbesc($importer["uid"])
1402 logger("Now import the DFRN feed");
1403 dfrn::import($xml,$r[0], true);
1409 function item_is_remote_self($contact, &$datarray) {
1412 if (!$contact['remote_self'])
1415 // Prevent the forwarding of posts that are forwarded
1416 if ($datarray["extid"] == NETWORK_DFRN)
1419 // Prevent to forward already forwarded posts
1420 if ($datarray["app"] == $a->get_hostname())
1423 // Only forward posts
1424 if ($datarray["verb"] != ACTIVITY_POST)
1427 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
1430 $datarray2 = $datarray;
1431 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
1432 if ($contact['remote_self'] == 2) {
1433 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
1434 intval($contact['uid']));
1436 $datarray['contact-id'] = $r[0]["id"];
1438 $datarray['owner-name'] = $r[0]["name"];
1439 $datarray['owner-link'] = $r[0]["url"];
1440 $datarray['owner-avatar'] = $r[0]["thumb"];
1442 $datarray['author-name'] = $datarray['owner-name'];
1443 $datarray['author-link'] = $datarray['owner-link'];
1444 $datarray['author-avatar'] = $datarray['owner-avatar'];
1447 if ($contact['network'] != NETWORK_FEED) {
1448 $datarray["guid"] = get_guid(32);
1449 unset($datarray["plink"]);
1450 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
1451 $datarray["parent-uri"] = $datarray["uri"];
1452 $datarray["extid"] = $contact['network'];
1453 $urlpart = parse_url($datarray2['author-link']);
1454 $datarray["app"] = $urlpart["host"];
1456 $datarray['private'] = 0;
1459 if ($contact['network'] != NETWORK_FEED) {
1460 // Store the original post
1461 $r = item_store($datarray2, false, false);
1462 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
1464 $datarray["app"] = "Feed";
1469 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
1470 $url = notags(trim($datarray['author-link']));
1471 $name = notags(trim($datarray['author-name']));
1472 $photo = notags(trim($datarray['author-avatar']));
1474 if (is_object($item)) {
1475 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
1476 if ($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
1477 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
1481 if (is_array($contact)) {
1482 if (($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
1483 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
1484 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
1485 intval(CONTACT_IS_FRIEND),
1486 intval($contact['id']),
1487 intval($importer['uid'])
1490 // send email notification to owner?
1493 // create contact record
1495 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
1496 `blocked`, `readonly`, `pending`, `writable`)
1497 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
1498 intval($importer['uid']),
1499 dbesc(datetime_convert()),
1501 dbesc(normalise_link($url)),
1505 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
1506 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
1508 $r = q("SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
1509 intval($importer['uid']),
1513 $contact_record = $r[0];
1514 update_contact_avatar($photo, $importer["uid"], $contact_record["id"], true);
1518 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
1519 intval($importer['uid'])
1522 if (count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1524 // create notification
1525 $hash = random_string();
1527 if (is_array($contact_record)) {
1528 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
1529 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
1530 intval($importer['uid']),
1531 intval($contact_record['id']),
1533 dbesc(datetime_convert())
1537 $def_gid = get_default_group($importer['uid'], $contact_record["network"]);
1539 if (intval($def_gid))
1540 group_add_member($importer['uid'],'',$contact_record['id'],$def_gid);
1542 if (($r[0]['notify-flags'] & NOTIFY_INTRO) &&
1543 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
1546 'type' => NOTIFY_INTRO,
1547 'notify_flags' => $r[0]['notify-flags'],
1548 'language' => $r[0]['language'],
1549 'to_name' => $r[0]['username'],
1550 'to_email' => $r[0]['email'],
1551 'uid' => $r[0]['uid'],
1552 'link' => $a->get_baseurl() . '/notifications/intro',
1553 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
1554 'source_link' => $contact_record['url'],
1555 'source_photo' => $contact_record['photo'],
1556 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
1561 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1562 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
1563 intval($importer['uid']),
1571 function lose_follower($importer,$contact,$datarray = array(),$item = "") {
1573 if (($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
1574 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1575 intval(CONTACT_IS_SHARING),
1576 intval($contact['id'])
1579 contact_remove($contact['id']);
1583 function lose_sharer($importer,$contact,$datarray = array(),$item = "") {
1585 if (($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
1586 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1587 intval(CONTACT_IS_FOLLOWER),
1588 intval($contact['id'])
1591 contact_remove($contact['id']);
1595 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
1599 if (is_array($importer)) {
1600 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
1601 intval($importer['uid'])
1605 // Diaspora has different message-ids in feeds than they do
1606 // through the direct Diaspora protocol. If we try and use
1607 // the feed, we'll get duplicates. So don't.
1609 if ((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
1612 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
1614 // Use a single verify token, even if multiple hubs
1616 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
1618 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
1620 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
1622 if (!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
1623 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
1624 dbesc($verify_token),
1625 intval($contact['id'])
1629 post_url($url,$params);
1631 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
1637 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
1639 if (get_config('system','disable_embedded'))
1644 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
1645 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
1650 $img_start = strpos($orig_body, '[img');
1651 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1652 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1653 while( ($img_st_close !== false) && ($img_len !== false) ) {
1655 $img_st_close++; // make it point to AFTER the closing bracket
1656 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
1658 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
1661 if (stristr($image , $site . '/photo/')) {
1662 // Only embed locally hosted photos
1664 $i = basename($image);
1665 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
1666 $x = strpos($i,'-');
1669 $res = substr($i,$x+1);
1670 $i = substr($i,0,$x);
1671 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
1678 // Check to see if we should replace this photo link with an embedded image
1679 // 1. No need to do so if the photo is public
1680 // 2. If there's a contact-id provided, see if they're in the access list
1681 // for the photo. If so, embed it.
1682 // 3. Otherwise, if we have an item, see if the item permissions match the photo
1683 // permissions, regardless of order but first check to see if they're an exact
1684 // match to save some processing overhead.
1686 if (has_permissions($r[0])) {
1688 $recips = enumerate_permissions($r[0]);
1689 if (in_array($cid, $recips)) {
1693 if (compare_permissions($item,$r[0]))
1698 $data = $r[0]['data'];
1699 $type = $r[0]['type'];
1701 // If a custom width and height were specified, apply before embedding
1702 if (preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
1703 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
1705 $width = intval($match[1]);
1706 $height = intval($match[2]);
1708 $ph = new Photo($data, $type);
1709 if ($ph->is_valid()) {
1710 $ph->scaleImage(max($width, $height));
1711 $data = $ph->imageString();
1712 $type = $ph->getType();
1716 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
1717 $image = 'data:' . $type . ';base64,' . base64_encode($data);
1718 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
1724 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
1725 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
1726 if ($orig_body === false)
1729 $img_start = strpos($orig_body, '[img');
1730 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1731 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1734 $new_body = $new_body . $orig_body;
1739 function has_permissions($obj) {
1740 if (($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
1745 function compare_permissions($obj1,$obj2) {
1746 // first part is easy. Check that these are exactly the same.
1747 if (($obj1['allow_cid'] == $obj2['allow_cid'])
1748 && ($obj1['allow_gid'] == $obj2['allow_gid'])
1749 && ($obj1['deny_cid'] == $obj2['deny_cid'])
1750 && ($obj1['deny_gid'] == $obj2['deny_gid']))
1753 // This is harder. Parse all the permissions and compare the resulting set.
1755 $recipients1 = enumerate_permissions($obj1);
1756 $recipients2 = enumerate_permissions($obj2);
1759 if ($recipients1 == $recipients2)
1764 // returns an array of contact-ids that are allowed to see this object
1766 function enumerate_permissions($obj) {
1767 $allow_people = expand_acl($obj['allow_cid']);
1768 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
1769 $deny_people = expand_acl($obj['deny_cid']);
1770 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
1771 $recipients = array_unique(array_merge($allow_people,$allow_groups));
1772 $deny = array_unique(array_merge($deny_people,$deny_groups));
1773 $recipients = array_diff($recipients,$deny);
1777 function item_getfeedtags($item) {
1780 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1782 for($x = 0; $x < $cnt; $x ++) {
1783 if ($matches[1][$x])
1784 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
1788 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1790 for($x = 0; $x < $cnt; $x ++) {
1791 if ($matches[1][$x])
1792 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
1798 function item_expire($uid, $days, $network = "", $force = false) {
1800 if ((! $uid) || ($days < 1))
1803 // $expire_network_only = save your own wall posts
1804 // and just expire conversations started by others
1806 $expire_network_only = get_pconfig($uid,'expire','network_only');
1807 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
1809 if ($network != "") {
1810 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
1811 // There is an index "uid_network_received" but not "uid_network_created"
1812 // This avoids the creation of another index just for one purpose.
1813 // And it doesn't really matter wether to look at "received" or "created"
1814 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1816 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1818 $r = q("SELECT `file`, `resource-id`, `starred`, `type`, `id` FROM `item`
1819 WHERE `uid` = %d $range
1830 $expire_items = get_pconfig($uid, 'expire','items');
1831 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
1833 // Forcing expiring of items - but not notes and marked items
1835 $expire_items = true;
1837 $expire_notes = get_pconfig($uid, 'expire','notes');
1838 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
1840 $expire_starred = get_pconfig($uid, 'expire','starred');
1841 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
1843 $expire_photos = get_pconfig($uid, 'expire','photos');
1844 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
1846 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
1848 foreach($r as $item) {
1850 // don't expire filed items
1852 if (strpos($item['file'],'[') !== false)
1855 // Only expire posts, not photos and photo comments
1857 if ($expire_photos==0 && strlen($item['resource-id']))
1859 if ($expire_starred==0 && intval($item['starred']))
1861 if ($expire_notes==0 && $item['type']=='note')
1863 if ($expire_items==0 && $item['type']!='note')
1866 drop_item($item['id'],false);
1869 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1874 function drop_items($items) {
1877 if (! local_user() && ! remote_user())
1880 if (count($items)) {
1881 foreach($items as $item) {
1882 $owner = drop_item($item,false);
1883 if ($owner && ! $uid)
1888 // multiple threads may have been deleted, send an expire notification
1891 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1895 function drop_item($id,$interactive = true) {
1899 // locate item to be deleted
1901 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
1908 notice( t('Item not found.') . EOL);
1909 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1914 $owner = $item['uid'];
1918 // check if logged in user is either the author or owner of this item
1920 if (is_array($_SESSION['remote'])) {
1921 foreach($_SESSION['remote'] as $visitor) {
1922 if ($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
1923 $contact_id = $visitor['cid'];
1930 if ((local_user() == $item['uid']) || ($contact_id) || (! $interactive)) {
1932 // Check if we should do HTML-based delete confirmation
1933 if ($_REQUEST['confirm']) {
1934 // <form> can't take arguments in its "action" parameter
1935 // so add any arguments as hidden inputs
1936 $query = explode_querystring($a->query_string);
1938 foreach($query['args'] as $arg) {
1939 if (strpos($arg, 'confirm=') === false) {
1940 $arg_parts = explode('=', $arg);
1941 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
1945 return replace_macros(get_markup_template('confirm.tpl'), array(
1947 '$message' => t('Do you really want to delete this item?'),
1948 '$extra_inputs' => $inputs,
1949 '$confirm' => t('Yes'),
1950 '$confirm_url' => $query['base'],
1951 '$confirm_name' => 'confirmed',
1952 '$cancel' => t('Cancel'),
1955 // Now check how the user responded to the confirmation query
1956 if ($_REQUEST['canceled']) {
1957 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1960 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
1963 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
1964 dbesc(datetime_convert()),
1965 dbesc(datetime_convert()),
1968 create_tags_from_item($item['id']);
1969 create_files_from_item($item['id']);
1970 delete_thread($item['id'], $item['parent-uri']);
1972 // clean up categories and tags so they don't end up as orphans
1975 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1977 foreach($matches as $mtch) {
1978 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
1984 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1986 foreach($matches as $mtch) {
1987 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
1991 // If item is a link to a photo resource, nuke all the associated photos
1992 // (visitors will not have photo resources)
1993 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
1994 // generate a resource-id and therefore aren't intimately linked to the item.
1996 if (strlen($item['resource-id'])) {
1997 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
1998 dbesc($item['resource-id']),
1999 intval($item['uid'])
2001 // ignore the result
2004 // If item is a link to an event, nuke the event record.
2006 if (intval($item['event-id'])) {
2007 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
2008 intval($item['event-id']),
2009 intval($item['uid'])
2011 // ignore the result
2014 // If item has attachments, drop them
2016 foreach(explode(",",$item['attach']) as $attach){
2017 preg_match("|attach/(\d+)|", $attach, $matches);
2018 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
2019 intval($matches[1]),
2022 // ignore the result
2026 // clean up item_id and sign meta-data tables
2029 // Old code - caused very long queries and warning entries in the mysql logfiles:
2031 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
2032 intval($item['id']),
2033 intval($item['uid'])
2036 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
2037 intval($item['id']),
2038 intval($item['uid'])
2042 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
2044 // Creating list of parents
2045 $r = q("select id from item where parent = %d and uid = %d",
2046 intval($item['id']),
2047 intval($item['uid'])
2052 foreach ($r AS $row) {
2053 if ($parentid != "")
2056 $parentid .= $row["id"];
2060 if ($parentid != "") {
2061 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
2063 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
2066 // If it's the parent of a comment thread, kill all the kids
2068 if ($item['uri'] == $item['parent-uri']) {
2069 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
2070 WHERE `parent-uri` = '%s' AND `uid` = %d ",
2071 dbesc(datetime_convert()),
2072 dbesc(datetime_convert()),
2073 dbesc($item['parent-uri']),
2074 intval($item['uid'])
2076 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
2077 create_files_from_itemuri($item['parent-uri'], $item['uid']);
2078 delete_thread_uri($item['parent-uri'], $item['uid']);
2079 // ignore the result
2081 // ensure that last-child is set in case the comment that had it just got wiped.
2082 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2083 dbesc(datetime_convert()),
2084 dbesc($item['parent-uri']),
2085 intval($item['uid'])
2087 // who is the last child now?
2088 $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",
2089 dbesc($item['parent-uri']),
2090 intval($item['uid'])
2093 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2099 $drop_id = intval($item['id']);
2101 // send the notification upstream/downstream as the case may be
2103 proc_run(PRIORITY_HIGH,"include/notifier.php", "drop", $drop_id);
2107 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2112 notice( t('Permission denied.') . EOL);
2113 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2120 function first_post_date($uid,$wall = false) {
2121 $r = q("select id, created from item
2122 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
2124 order by created asc limit 1",
2126 intval($wall ? 1 : 0)
2129 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
2130 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
2135 /* modified posted_dates() {below} to arrange the list in years */
2136 function list_post_dates($uid, $wall) {
2137 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2139 $dthen = first_post_date($uid, $wall);
2143 // Set the start and end date to the beginning of the month
2144 $dnow = substr($dnow,0,8).'01';
2145 $dthen = substr($dthen,0,8).'01';
2149 // Starting with the current month, get the first and last days of every
2150 // month down to and including the month of the first post
2151 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2152 $dyear = intval(substr($dnow,0,4));
2153 $dstart = substr($dnow,0,8) . '01';
2154 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2155 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2156 $end_month = datetime_convert('','',$dend,'Y-m-d');
2157 $str = day_translate(datetime_convert('','',$dnow,'F'));
2159 $ret[$dyear] = array();
2160 $ret[$dyear][] = array($str,$end_month,$start_month);
2161 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2166 function posted_dates($uid,$wall) {
2167 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2169 $dthen = first_post_date($uid,$wall);
2173 // Set the start and end date to the beginning of the month
2174 $dnow = substr($dnow,0,8).'01';
2175 $dthen = substr($dthen,0,8).'01';
2178 // Starting with the current month, get the first and last days of every
2179 // month down to and including the month of the first post
2180 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2181 $dstart = substr($dnow,0,8) . '01';
2182 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2183 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2184 $end_month = datetime_convert('','',$dend,'Y-m-d');
2185 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
2186 $ret[] = array($str,$end_month,$start_month);
2187 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2193 function posted_date_widget($url,$uid,$wall) {
2196 if (! feature_enabled($uid,'archives'))
2199 // For former Facebook folks that left because of "timeline"
2201 /* if ($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
2204 $visible_years = get_pconfig($uid,'system','archive_visible_years');
2205 if (! $visible_years)
2208 $ret = list_post_dates($uid,$wall);
2213 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
2214 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
2216 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
2217 '$title' => t('Archives'),
2218 '$size' => $visible_years,
2219 '$cutoff_year' => $cutoff_year,
2220 '$cutoff' => $cutoff,
2223 '$showmore' => t('show more')