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) {
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);
369 $guid_prefix = hash("crc32", $parsed["host"]);
371 // Remove the scheme to make sure that "https" and "http" doesn't make a difference
372 unset($parsed["scheme"]);
374 $host_id = implode("/", $parsed);
376 // We could use any hash algorithm since it isn't a security issue
377 $host_hash = hash("ripemd128", $host_id);
379 return $guid_prefix.$host_hash;
382 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
384 // If it is a posting where users should get notifications, then define it as wall posting
387 $arr['type'] = 'wall';
389 $arr['last-child'] = 1;
390 $arr['network'] = NETWORK_DFRN;
393 // If a Diaspora signature structure was passed in, pull it out of the
394 // item array and set it aside for later storage.
397 if (x($arr,'dsprsig')) {
398 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
399 unset($arr['dsprsig']);
402 // Converting the plink
403 if ($arr['network'] == NETWORK_OSTATUS) {
404 if (isset($arr['plink']))
405 $arr['plink'] = ostatus::convert_href($arr['plink']);
406 elseif (isset($arr['uri']))
407 $arr['plink'] = ostatus::convert_href($arr['uri']);
410 if (x($arr, 'gravity'))
411 $arr['gravity'] = intval($arr['gravity']);
412 elseif ($arr['parent-uri'] === $arr['uri'])
414 elseif (activity_match($arr['verb'],ACTIVITY_POST))
417 $arr['gravity'] = 6; // extensible catchall
419 if (! x($arr,'type'))
420 $arr['type'] = 'remote';
424 /* check for create date and expire time */
425 $uid = intval($arr['uid']);
426 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
428 $expire_interval = $r[0]['expire'];
429 if ($expire_interval>0) {
430 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
431 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
432 if ($created_date < $expire_date) {
433 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
439 // Do we already have this item?
440 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
441 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
442 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
443 dbesc(trim($arr['uri'])),
445 dbesc(NETWORK_DIASPORA),
447 dbesc(NETWORK_OSTATUS)
450 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
452 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']);
457 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
458 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
459 //if ((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
460 // $arr['body'] = strip_tags($arr['body']);
462 item_add_language_opt($arr);
466 elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
467 $arr['guid'] = uri_to_guid($arr['plink']);
468 elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
469 $arr['guid'] = uri_to_guid($arr['uri']);
471 $parsed = parse_url($arr["author-link"]);
472 $guid_prefix = hash("crc32", $parsed["host"]);
475 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
476 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
477 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
478 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
479 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
480 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
481 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
482 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
483 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
484 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
485 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
486 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
487 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
488 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
489 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
490 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
491 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
492 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
493 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
494 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
496 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
497 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
498 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
499 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
500 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
501 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
502 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
503 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
504 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
505 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
506 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
507 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
508 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
509 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
510 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
511 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
512 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
513 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
514 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
515 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
516 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
517 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
518 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
519 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
521 // Items cannot be stored before they happen ...
522 if ($arr['created'] > datetime_convert())
523 $arr['created'] = datetime_convert();
525 // We haven't invented time travel by now.
526 if ($arr['edited'] > datetime_convert())
527 $arr['edited'] = datetime_convert();
529 if (($arr['author-link'] == "") AND ($arr['owner-link'] == ""))
530 logger("Both author-link and owner-link are empty. Called by: ".App::callstack(), LOGGER_DEBUG);
532 if ($arr['plink'] == "") {
534 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
537 if ($arr['network'] == "") {
538 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
539 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
540 dbesc(normalise_link($arr['author-link'])),
545 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
546 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
547 dbesc(normalise_link($arr['author-link']))
551 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
552 intval($arr['contact-id']),
557 $arr['network'] = $r[0]["network"];
559 // Fallback to friendica (why is it empty in some cases?)
560 if ($arr['network'] == "")
561 $arr['network'] = NETWORK_DFRN;
563 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
566 // The contact-id should be set before "item_store" was called - but there seems to be some issues
567 if ($arr["contact-id"] == 0) {
568 // First we are looking for a suitable contact that matches with the author of the post
569 // This is done only for comments (See below explanation at "gcontact-id")
570 if ($arr['parent-uri'] != $arr['uri'])
571 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
573 // If not present then maybe the owner was found
574 if ($arr["contact-id"] == 0)
575 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
577 // Still missing? Then use the "self" contact of the current user
578 if ($arr["contact-id"] == 0) {
579 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
581 $arr["contact-id"] = $r[0]["id"];
583 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
586 if ($arr["gcontact-id"] == 0) {
587 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
588 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
589 // On comments the author is the better choice.
590 if ($arr['parent-uri'] === $arr['uri'])
591 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
592 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
594 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
595 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
598 if ($arr["author-id"] == 0)
599 $arr["author-id"] = get_contact($arr["author-link"], 0);
601 if ($arr["owner-id"] == 0)
602 $arr["owner-id"] = get_contact($arr["owner-link"], 0);
604 if ($arr['guid'] != "") {
605 // Checking if there is already an item with the same guid
606 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
607 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
608 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
611 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
616 // Check for hashtags in the body and repair or add hashtag links
617 item_body_set_hashtags($arr);
619 $arr['thr-parent'] = $arr['parent-uri'];
620 if ($arr['parent-uri'] === $arr['uri']) {
623 $allow_cid = $arr['allow_cid'];
624 $allow_gid = $arr['allow_gid'];
625 $deny_cid = $arr['deny_cid'];
626 $deny_gid = $arr['deny_gid'];
627 $notify_type = 'wall-new';
630 // find the parent and snarf the item id and ACLs
631 // and anything else we need to inherit
633 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
634 dbesc($arr['parent-uri']),
640 // is the new message multi-level threaded?
641 // even though we don't support it now, preserve the info
642 // and re-attach to the conversation parent.
644 if ($r[0]['uri'] != $r[0]['parent-uri']) {
645 $arr['parent-uri'] = $r[0]['parent-uri'];
646 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
647 ORDER BY `id` ASC LIMIT 1",
648 dbesc($r[0]['parent-uri']),
649 dbesc($r[0]['parent-uri']),
656 $parent_id = $r[0]['id'];
657 $parent_deleted = $r[0]['deleted'];
658 $allow_cid = $r[0]['allow_cid'];
659 $allow_gid = $r[0]['allow_gid'];
660 $deny_cid = $r[0]['deny_cid'];
661 $deny_gid = $r[0]['deny_gid'];
662 $arr['wall'] = $r[0]['wall'];
663 $notify_type = 'comment-new';
665 // if the parent is private, force privacy for the entire conversation
666 // This differs from the above settings as it subtly allows comments from
667 // email correspondents to be private even if the overall thread is not.
669 if ($r[0]['private'])
670 $arr['private'] = $r[0]['private'];
672 // Edge case. We host a public forum that was originally posted to privately.
673 // The original author commented, but as this is a comment, the permissions
674 // weren't fixed up so it will still show the comment as private unless we fix it here.
676 if ((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
680 // If its a post from myself then tag the thread as "mention"
681 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
682 $u = q("SELECT `nickname` FROM `user` WHERE `uid` = %d", intval($arr['uid']));
685 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
686 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
687 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
688 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
689 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
694 // Allow one to see reply tweets from status.net even when
695 // we don't have or can't see the original post.
698 logger('item_store: $force_parent=true, reply converted to top-level post.');
700 $arr['parent-uri'] = $arr['uri'];
703 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
711 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
713 dbesc($arr['network']),
717 if (dbm::is_result($r)) {
718 logger('duplicated item with the same uri found. '.print_r($arr,true));
722 // On Friendica and Diaspora the GUID is unique
723 if (in_array($arr['network'], array(NETWORK_DFRN, NETWORK_DIASPORA))) {
724 $r = q("SELECT `id` FROM `item` WHERE `guid` = '%s' AND `uid` = %d LIMIT 1",
728 if (dbm::is_result($r)) {
729 logger('duplicated item with the same guid found. '.print_r($arr,true));
733 // Check for an existing post with the same content. There seems to be a problem with OStatus.
734 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
736 dbesc($arr['network']),
737 dbesc($arr['created']),
738 intval($arr['contact-id']),
741 if (dbm::is_result($r)) {
742 logger('duplicated item with the same body found. '.print_r($arr,true));
747 // Is this item available in the global items (with uid=0)?
748 if ($arr["uid"] == 0) {
749 $arr["global"] = true;
751 // Set the global flag on all items if this was a global item entry
752 q("UPDATE `item` SET `global` = 1 WHERE `uri` = '%s'", dbesc($arr["uri"]));
754 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `uri` = '%s'", dbesc($arr["uri"]));
756 $arr["global"] = (count($isglobal) > 0);
760 if (strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
763 $private = $arr['private'];
765 $arr["allow_cid"] = $allow_cid;
766 $arr["allow_gid"] = $allow_gid;
767 $arr["deny_cid"] = $deny_cid;
768 $arr["deny_gid"] = $deny_gid;
769 $arr["private"] = $private;
770 $arr["deleted"] = $parent_deleted;
772 // Fill the cache field
773 put_item_in_cache($arr);
776 call_hooks('post_local',$arr);
778 call_hooks('post_remote',$arr);
780 if (x($arr,'cancel')) {
781 logger('item_store: post cancelled by plugin.');
785 // Check for already added items.
786 // There is a timing issue here that sometimes creates double postings.
787 // An unique index would help - but the limitations of MySQL (maximum size of index values) prevent this.
788 if ($arr["uid"] == 0) {
789 $r = qu("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = 0 LIMIT 1", dbesc(trim($arr['uri'])));
790 if (dbm::is_result($r)) {
791 logger('Global item already stored. URI: '.$arr['uri'].' on network '.$arr['network'], LOGGER_DEBUG);
796 // Store the unescaped version
801 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
804 q("START TRANSACTION;");
806 $r = dbq("INSERT INTO `item` (`"
807 . implode("`, `", array_keys($arr))
809 . implode("', '", array_values($arr))
815 // When the item was successfully stored we fetch the ID of the item.
816 if (dbm::is_result($r)) {
817 $r = q("SELECT LAST_INSERT_ID() AS `item-id`");
818 if (dbm::is_result($r)) {
819 $current_post = $r[0]['item-id'];
821 // This shouldn't happen
825 // This can happen - for example - if there are locking timeouts.
826 logger("Item wasn't stored - we quit here.");
831 if ($current_post == 0) {
832 // This is one of these error messages that never should occur.
833 logger("couldn't find created item - we better quit now.");
838 // How much entries have we created?
839 // We wouldn't need this query when we could use an unique index - but MySQL has length problems with them.
840 $r = q("SELECT COUNT(*) AS `entries` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s'",
843 dbesc($arr['network'])
846 if (!dbm::is_result($r)) {
847 // This shouldn't happen, since COUNT always works when the database connection is there.
848 logger("We couldn't count the stored entries. Very strange ...");
853 if ($r[0]["entries"] > 1) {
854 // There are duplicates. We delete our just created entry.
855 logger('Duplicated post occurred. uri = '.$arr['uri'].' uid = '.$arr['uid']);
857 // Yes, we could do a rollback here - but we are having many users with MyISAM.
858 q("DELETE FROM `item` WHERE `id` = %d", intval($current_post));
861 } elseif ($r[0]["entries"] == 0) {
862 // This really should never happen since we quit earlier if there were problems.
863 logger("Something is terribly wrong. We haven't found our created entry.");
868 logger('item_store: created item '.$current_post);
869 item_set_last_item($arr);
871 if (!$parent_id || ($arr['parent-uri'] === $arr['uri']))
872 $parent_id = $current_post;
875 $r = q("UPDATE `item` SET `parent` = %d WHERE `id` = %d",
877 intval($current_post)
880 $arr['id'] = $current_post;
881 $arr['parent'] = $parent_id;
883 // update the commented timestamp on the parent
884 // Only update "commented" if it is really a comment
885 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
886 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
887 dbesc(datetime_convert()),
888 dbesc(datetime_convert()),
892 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
893 dbesc(datetime_convert()),
899 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
900 // We can check for this condition when we decode and encode the stuff again.
901 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
902 $dsprsig->signature = base64_decode($dsprsig->signature);
903 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
906 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
907 intval($current_post),
908 dbesc($dsprsig->signed_text),
909 dbesc($dsprsig->signature),
910 dbesc($dsprsig->signer)
914 $deleted = tag_deliver($arr['uid'],$current_post);
916 // current post can be deleted if is for a community page and no mention are
918 if (!$deleted AND !$dontcache) {
920 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
921 if (count($r) == 1) {
923 call_hooks('post_local_end', $r[0]);
925 call_hooks('post_remote_end', $r[0]);
927 logger('item_store: new item not found in DB, id ' . $current_post);
930 if ($arr['parent-uri'] === $arr['uri']) {
931 add_thread($current_post);
933 update_thread($parent_id);
938 // Due to deadlock issues with the "term" table we are doing these steps after the commit.
939 // This is not perfect - but a workable solution until we found the reason for the problem.
940 create_tags_from_item($current_post);
941 create_files_from_item($current_post);
943 // If this is now the last-child, force all _other_ children of this parent to *not* be last-child
944 // It is done after the transaction to avoid dead locks.
945 if ($arr['last-child']) {
946 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
949 intval($current_post)
953 if ($arr['parent-uri'] === $arr['uri']) {
954 add_shadow_thread($current_post);
956 add_shadow_entry($current_post);
959 check_item_notification($current_post, $uid);
962 proc_run(PRIORITY_HIGH, "include/notifier.php", $notify_type, $current_post);
964 return $current_post;
968 * @brief Set "success_update" and "last-item" to the date of the last time we heard from this contact
970 * This can be used to filter for inactive contacts.
971 * Only do this for public postings to avoid privacy problems, since poco data is public.
972 * Don't set this value if it isn't from the owner (could be an author that we don't know)
974 * @param array $arr Contains the just posted item record
976 function item_set_last_item($arr) {
978 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
980 // Is it a forum? Then we don't care about the rules from above
981 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
982 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
983 intval($arr['contact-id']));
990 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
991 dbesc($arr['received']),
992 dbesc($arr['received']),
993 intval($arr['contact-id'])
996 // Now do the same for the system wide contacts with uid=0
997 if (!$arr['private']) {
998 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
999 dbesc($arr['received']),
1000 dbesc($arr['received']),
1001 intval($arr['owner-id'])
1004 if ($arr['owner-id'] != $arr['author-id']) {
1005 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1006 dbesc($arr['received']),
1007 dbesc($arr['received']),
1008 intval($arr['author-id'])
1014 function item_body_set_hashtags(&$item) {
1016 $tags = get_tags($item["body"]);
1022 // This sorting is important when there are hashtags that are part of other hashtags
1023 // Otherwise there could be problems with hashtags like #test and #test2
1028 $URLSearchString = "^\[\]";
1030 // All hashtags should point to the home server
1031 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1032 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1034 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1035 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1037 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1038 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1040 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1043 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1045 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1048 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1050 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1053 // Repair recursive urls
1054 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1055 "#$2", $item["body"]);
1058 foreach($tags as $tag) {
1059 if (strpos($tag,'#') !== 0)
1062 if (strpos($tag,'[url='))
1065 $basetag = str_replace('_',' ',substr($tag,1));
1067 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1069 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1071 if (!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1072 if (strlen($item["tag"]))
1073 $item["tag"] = ','.$item["tag"];
1074 $item["tag"] = $newtag.$item["tag"];
1078 // Convert back the masked hashtags
1079 $item["body"] = str_replace("#", "#", $item["body"]);
1082 function get_item_guid($id) {
1083 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1085 return($r[0]["guid"]);
1090 function get_item_id($guid, $uid = 0) {
1096 $uid == local_user();
1098 // Does the given user have this item?
1100 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1101 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1102 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1105 $nick = $r[0]["nickname"];
1109 // Or is it anywhere on the server?
1111 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1112 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1113 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1114 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1115 AND `item`.`private` = 0 AND `item`.`wall` = 1
1116 AND `item`.`guid` = '%s'", dbesc($guid));
1119 $nick = $r[0]["nickname"];
1122 return(array("nick" => $nick, "id" => $id));
1126 function get_item_contact($item,$contacts) {
1127 if (! count($contacts) || (! is_array($item)))
1129 foreach($contacts as $contact) {
1130 if ($contact['id'] == $item['contact-id']) {
1132 break; // NOTREACHED
1139 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1141 * @param int $item_id
1142 * @return bool true if item was deleted, else false
1144 function tag_deliver($uid,$item_id) {
1152 $u = q("select * from user where uid = %d limit 1",
1158 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1159 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1162 $i = q("select * from item where id = %d and uid = %d limit 1",
1171 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1173 // Diaspora uses their own hardwired link URL in @-tags
1174 // instead of the one we supply with webfinger
1176 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1178 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1180 foreach($matches as $mtch) {
1181 if (link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1183 logger('tag_deliver: mention found: ' . $mtch[2]);
1189 if ( ($community_page || $prvgroup) &&
1190 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1191 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1193 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1194 q("DELETE FROM item WHERE id = %d and uid = %d",
1203 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1205 call_hooks('tagged', $arr);
1207 if ((! $community_page) && (! $prvgroup))
1211 // tgroup delivery - setup a second delivery chain
1212 // prevent delivery looping - only proceed
1213 // if the message originated elsewhere and is a top-level post
1215 if (($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1218 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1221 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1222 intval($u[0]['uid'])
1227 // also reset all the privacy bits to the forum default permissions
1229 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1231 $forum_mode = (($prvgroup) ? 2 : 1);
1233 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1234 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1235 intval($forum_mode),
1236 dbesc($c[0]['name']),
1237 dbesc($c[0]['url']),
1238 dbesc($c[0]['thumb']),
1240 dbesc($u[0]['allow_cid']),
1241 dbesc($u[0]['allow_gid']),
1242 dbesc($u[0]['deny_cid']),
1243 dbesc($u[0]['deny_gid']),
1246 update_thread($item_id);
1248 proc_run(PRIORITY_HIGH,'include/notifier.php', 'tgroup', $item_id);
1254 function tgroup_check($uid,$item) {
1260 // check that the message originated elsewhere and is a top-level post
1262 if (($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1266 $u = q("select * from user where uid = %d limit 1",
1272 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1273 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1276 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1278 // Diaspora uses their own hardwired link URL in @-tags
1279 // instead of the one we supply with webfinger
1281 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1283 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1285 foreach($matches as $mtch) {
1286 if (link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1288 logger('tgroup_check: mention found: ' . $mtch[2]);
1296 if ((! $community_page) && (! $prvgroup))
1303 This function returns true if $update has an edited timestamp newer
1304 than $existing, i.e. $update contains new data which should override
1305 what's already there. If there is no timestamp yet, the update is
1306 assumed to be newer. If the update has no timestamp, the existing
1307 item is assumed to be up-to-date. If the timestamps are equal it
1308 assumes the update has been seen before and should be ignored.
1310 function edited_timestamp_is_newer($existing, $update) {
1311 if (!x($existing,'edited') || !$existing['edited']) {
1314 if (!x($update,'edited') || !$update['edited']) {
1317 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1318 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1319 return (strcmp($existing_edited, $update_edited) < 0);
1324 * consume_feed - process atom feed and update anything/everything we might need to update
1326 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1328 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1329 * It is this person's stuff that is going to be updated.
1330 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1331 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1332 * have a contact record.
1333 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1334 * might not) try and subscribe to it.
1335 * $datedir sorts in reverse order
1336 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1337 * imported prior to its children being seen in the stream unless we are certain
1338 * of how the feed is arranged/ordered.
1339 * With $pass = 1, we only pull parent items out of the stream.
1340 * With $pass = 2, we only pull children (comments/likes).
1342 * So running this twice, first with pass 1 and then with pass 2 will do the right
1343 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1344 * model where comments can have sub-threads. That would require some massive sorting
1345 * to get all the feed items into a mostly linear ordering, and might still require
1349 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1350 if ($contact['network'] === NETWORK_OSTATUS) {
1352 // Test - remove before flight
1353 //$tempfile = tempnam(get_temppath(), "ostatus2");
1354 //file_put_contents($tempfile, $xml);
1355 logger("Consume OStatus messages ", LOGGER_DEBUG);
1356 ostatus::import($xml,$importer,$contact, $hub);
1361 if ($contact['network'] === NETWORK_FEED) {
1363 logger("Consume feeds", LOGGER_DEBUG);
1364 feed_import($xml,$importer,$contact, $hub);
1369 if ($contact['network'] === NETWORK_DFRN) {
1370 logger("Consume DFRN messages", LOGGER_DEBUG);
1372 $r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
1373 `contact`.`pubkey` AS `cpubkey`,
1374 `contact`.`prvkey` AS `cprvkey`,
1375 `contact`.`thumb` AS `thumb`,
1376 `contact`.`url` as `url`,
1377 `contact`.`name` as `senderName`,
1380 LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
1381 WHERE `contact`.`id` = %d AND `user`.`uid` = %d",
1382 dbesc($contact["id"]), dbesc($importer["uid"])
1385 logger("Now import the DFRN feed");
1386 dfrn::import($xml,$r[0], true);
1392 function item_is_remote_self($contact, &$datarray) {
1395 if (!$contact['remote_self'])
1398 // Prevent the forwarding of posts that are forwarded
1399 if ($datarray["extid"] == NETWORK_DFRN)
1402 // Prevent to forward already forwarded posts
1403 if ($datarray["app"] == $a->get_hostname())
1406 // Only forward posts
1407 if ($datarray["verb"] != ACTIVITY_POST)
1410 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
1413 $datarray2 = $datarray;
1414 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
1415 if ($contact['remote_self'] == 2) {
1416 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
1417 intval($contact['uid']));
1419 $datarray['contact-id'] = $r[0]["id"];
1421 $datarray['owner-name'] = $r[0]["name"];
1422 $datarray['owner-link'] = $r[0]["url"];
1423 $datarray['owner-avatar'] = $r[0]["thumb"];
1425 $datarray['author-name'] = $datarray['owner-name'];
1426 $datarray['author-link'] = $datarray['owner-link'];
1427 $datarray['author-avatar'] = $datarray['owner-avatar'];
1430 if ($contact['network'] != NETWORK_FEED) {
1431 $datarray["guid"] = get_guid(32);
1432 unset($datarray["plink"]);
1433 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
1434 $datarray["parent-uri"] = $datarray["uri"];
1435 $datarray["extid"] = $contact['network'];
1436 $urlpart = parse_url($datarray2['author-link']);
1437 $datarray["app"] = $urlpart["host"];
1439 $datarray['private'] = 0;
1442 if ($contact['network'] != NETWORK_FEED) {
1443 // Store the original post
1444 $r = item_store($datarray2, false, false);
1445 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
1447 $datarray["app"] = "Feed";
1452 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
1453 $url = notags(trim($datarray['author-link']));
1454 $name = notags(trim($datarray['author-name']));
1455 $photo = notags(trim($datarray['author-avatar']));
1457 if (is_object($item)) {
1458 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
1459 if ($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
1460 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
1464 if (is_array($contact)) {
1465 if (($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
1466 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
1467 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
1468 intval(CONTACT_IS_FRIEND),
1469 intval($contact['id']),
1470 intval($importer['uid'])
1473 // send email notification to owner?
1476 // create contact record
1478 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
1479 `blocked`, `readonly`, `pending`, `writable`)
1480 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
1481 intval($importer['uid']),
1482 dbesc(datetime_convert()),
1484 dbesc(normalise_link($url)),
1488 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
1489 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
1491 $r = q("SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
1492 intval($importer['uid']),
1496 $contact_record = $r[0];
1497 update_contact_avatar($photo, $importer["uid"], $contact_record["id"], true);
1501 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
1502 intval($importer['uid'])
1505 if (count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1507 // create notification
1508 $hash = random_string();
1510 if (is_array($contact_record)) {
1511 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
1512 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
1513 intval($importer['uid']),
1514 intval($contact_record['id']),
1516 dbesc(datetime_convert())
1520 $def_gid = get_default_group($importer['uid'], $contact_record["network"]);
1522 if (intval($def_gid))
1523 group_add_member($importer['uid'],'',$contact_record['id'],$def_gid);
1525 if (($r[0]['notify-flags'] & NOTIFY_INTRO) &&
1526 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
1529 'type' => NOTIFY_INTRO,
1530 'notify_flags' => $r[0]['notify-flags'],
1531 'language' => $r[0]['language'],
1532 'to_name' => $r[0]['username'],
1533 'to_email' => $r[0]['email'],
1534 'uid' => $r[0]['uid'],
1535 'link' => $a->get_baseurl() . '/notifications/intro',
1536 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
1537 'source_link' => $contact_record['url'],
1538 'source_photo' => $contact_record['photo'],
1539 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
1544 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1545 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
1546 intval($importer['uid']),
1554 function lose_follower($importer,$contact,$datarray = array(),$item = "") {
1556 if (($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
1557 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1558 intval(CONTACT_IS_SHARING),
1559 intval($contact['id'])
1562 contact_remove($contact['id']);
1566 function lose_sharer($importer,$contact,$datarray = array(),$item = "") {
1568 if (($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
1569 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1570 intval(CONTACT_IS_FOLLOWER),
1571 intval($contact['id'])
1574 contact_remove($contact['id']);
1578 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
1582 if (is_array($importer)) {
1583 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
1584 intval($importer['uid'])
1588 // Diaspora has different message-ids in feeds than they do
1589 // through the direct Diaspora protocol. If we try and use
1590 // the feed, we'll get duplicates. So don't.
1592 if ((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
1595 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
1597 // Use a single verify token, even if multiple hubs
1599 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
1601 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
1603 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
1605 if (!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
1606 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
1607 dbesc($verify_token),
1608 intval($contact['id'])
1612 post_url($url,$params);
1614 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
1620 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
1622 if (get_config('system','disable_embedded'))
1627 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
1628 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
1633 $img_start = strpos($orig_body, '[img');
1634 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1635 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1636 while( ($img_st_close !== false) && ($img_len !== false) ) {
1638 $img_st_close++; // make it point to AFTER the closing bracket
1639 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
1641 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
1644 if (stristr($image , $site . '/photo/')) {
1645 // Only embed locally hosted photos
1647 $i = basename($image);
1648 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
1649 $x = strpos($i,'-');
1652 $res = substr($i,$x+1);
1653 $i = substr($i,0,$x);
1654 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
1661 // Check to see if we should replace this photo link with an embedded image
1662 // 1. No need to do so if the photo is public
1663 // 2. If there's a contact-id provided, see if they're in the access list
1664 // for the photo. If so, embed it.
1665 // 3. Otherwise, if we have an item, see if the item permissions match the photo
1666 // permissions, regardless of order but first check to see if they're an exact
1667 // match to save some processing overhead.
1669 if (has_permissions($r[0])) {
1671 $recips = enumerate_permissions($r[0]);
1672 if (in_array($cid, $recips)) {
1676 if (compare_permissions($item,$r[0]))
1681 $data = $r[0]['data'];
1682 $type = $r[0]['type'];
1684 // If a custom width and height were specified, apply before embedding
1685 if (preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
1686 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
1688 $width = intval($match[1]);
1689 $height = intval($match[2]);
1691 $ph = new Photo($data, $type);
1692 if ($ph->is_valid()) {
1693 $ph->scaleImage(max($width, $height));
1694 $data = $ph->imageString();
1695 $type = $ph->getType();
1699 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
1700 $image = 'data:' . $type . ';base64,' . base64_encode($data);
1701 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
1707 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
1708 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
1709 if ($orig_body === false)
1712 $img_start = strpos($orig_body, '[img');
1713 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1714 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1717 $new_body = $new_body . $orig_body;
1722 function has_permissions($obj) {
1723 if (($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
1728 function compare_permissions($obj1,$obj2) {
1729 // first part is easy. Check that these are exactly the same.
1730 if (($obj1['allow_cid'] == $obj2['allow_cid'])
1731 && ($obj1['allow_gid'] == $obj2['allow_gid'])
1732 && ($obj1['deny_cid'] == $obj2['deny_cid'])
1733 && ($obj1['deny_gid'] == $obj2['deny_gid']))
1736 // This is harder. Parse all the permissions and compare the resulting set.
1738 $recipients1 = enumerate_permissions($obj1);
1739 $recipients2 = enumerate_permissions($obj2);
1742 if ($recipients1 == $recipients2)
1747 // returns an array of contact-ids that are allowed to see this object
1749 function enumerate_permissions($obj) {
1750 $allow_people = expand_acl($obj['allow_cid']);
1751 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
1752 $deny_people = expand_acl($obj['deny_cid']);
1753 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
1754 $recipients = array_unique(array_merge($allow_people,$allow_groups));
1755 $deny = array_unique(array_merge($deny_people,$deny_groups));
1756 $recipients = array_diff($recipients,$deny);
1760 function item_getfeedtags($item) {
1763 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1765 for($x = 0; $x < $cnt; $x ++) {
1766 if ($matches[1][$x])
1767 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
1771 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1773 for($x = 0; $x < $cnt; $x ++) {
1774 if ($matches[1][$x])
1775 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
1781 function item_expire($uid, $days, $network = "", $force = false) {
1783 if ((! $uid) || ($days < 1))
1786 // $expire_network_only = save your own wall posts
1787 // and just expire conversations started by others
1789 $expire_network_only = get_pconfig($uid,'expire','network_only');
1790 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
1792 if ($network != "") {
1793 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
1794 // There is an index "uid_network_received" but not "uid_network_created"
1795 // This avoids the creation of another index just for one purpose.
1796 // And it doesn't really matter wether to look at "received" or "created"
1797 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1799 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1801 $r = q("SELECT `file`, `resource-id`, `starred`, `type`, `id` FROM `item`
1802 WHERE `uid` = %d $range
1813 $expire_items = get_pconfig($uid, 'expire','items');
1814 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
1816 // Forcing expiring of items - but not notes and marked items
1818 $expire_items = true;
1820 $expire_notes = get_pconfig($uid, 'expire','notes');
1821 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
1823 $expire_starred = get_pconfig($uid, 'expire','starred');
1824 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
1826 $expire_photos = get_pconfig($uid, 'expire','photos');
1827 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
1829 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
1831 foreach($r as $item) {
1833 // don't expire filed items
1835 if (strpos($item['file'],'[') !== false)
1838 // Only expire posts, not photos and photo comments
1840 if ($expire_photos==0 && strlen($item['resource-id']))
1842 if ($expire_starred==0 && intval($item['starred']))
1844 if ($expire_notes==0 && $item['type']=='note')
1846 if ($expire_items==0 && $item['type']!='note')
1849 drop_item($item['id'],false);
1852 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1857 function drop_items($items) {
1860 if (! local_user() && ! remote_user())
1863 if (count($items)) {
1864 foreach($items as $item) {
1865 $owner = drop_item($item,false);
1866 if ($owner && ! $uid)
1871 // multiple threads may have been deleted, send an expire notification
1874 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1878 function drop_item($id,$interactive = true) {
1882 // locate item to be deleted
1884 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
1891 notice( t('Item not found.') . EOL);
1892 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1897 $owner = $item['uid'];
1901 // check if logged in user is either the author or owner of this item
1903 if (is_array($_SESSION['remote'])) {
1904 foreach($_SESSION['remote'] as $visitor) {
1905 if ($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
1906 $contact_id = $visitor['cid'];
1913 if ((local_user() == $item['uid']) || ($contact_id) || (! $interactive)) {
1915 // Check if we should do HTML-based delete confirmation
1916 if ($_REQUEST['confirm']) {
1917 // <form> can't take arguments in its "action" parameter
1918 // so add any arguments as hidden inputs
1919 $query = explode_querystring($a->query_string);
1921 foreach($query['args'] as $arg) {
1922 if (strpos($arg, 'confirm=') === false) {
1923 $arg_parts = explode('=', $arg);
1924 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
1928 return replace_macros(get_markup_template('confirm.tpl'), array(
1930 '$message' => t('Do you really want to delete this item?'),
1931 '$extra_inputs' => $inputs,
1932 '$confirm' => t('Yes'),
1933 '$confirm_url' => $query['base'],
1934 '$confirm_name' => 'confirmed',
1935 '$cancel' => t('Cancel'),
1938 // Now check how the user responded to the confirmation query
1939 if ($_REQUEST['canceled']) {
1940 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1943 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
1946 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
1947 dbesc(datetime_convert()),
1948 dbesc(datetime_convert()),
1951 create_tags_from_item($item['id']);
1952 create_files_from_item($item['id']);
1953 delete_thread($item['id'], $item['parent-uri']);
1955 // clean up categories and tags so they don't end up as orphans
1958 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1960 foreach($matches as $mtch) {
1961 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
1967 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1969 foreach($matches as $mtch) {
1970 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
1974 // If item is a link to a photo resource, nuke all the associated photos
1975 // (visitors will not have photo resources)
1976 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
1977 // generate a resource-id and therefore aren't intimately linked to the item.
1979 if (strlen($item['resource-id'])) {
1980 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
1981 dbesc($item['resource-id']),
1982 intval($item['uid'])
1984 // ignore the result
1987 // If item is a link to an event, nuke the event record.
1989 if (intval($item['event-id'])) {
1990 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
1991 intval($item['event-id']),
1992 intval($item['uid'])
1994 // ignore the result
1997 // If item has attachments, drop them
1999 foreach(explode(",",$item['attach']) as $attach){
2000 preg_match("|attach/(\d+)|", $attach, $matches);
2001 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
2002 intval($matches[1]),
2005 // ignore the result
2009 // clean up item_id and sign meta-data tables
2012 // Old code - caused very long queries and warning entries in the mysql logfiles:
2014 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
2015 intval($item['id']),
2016 intval($item['uid'])
2019 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
2020 intval($item['id']),
2021 intval($item['uid'])
2025 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
2027 // Creating list of parents
2028 $r = q("select id from item where parent = %d and uid = %d",
2029 intval($item['id']),
2030 intval($item['uid'])
2035 foreach ($r AS $row) {
2036 if ($parentid != "")
2039 $parentid .= $row["id"];
2043 if ($parentid != "") {
2044 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
2046 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
2049 // If it's the parent of a comment thread, kill all the kids
2051 if ($item['uri'] == $item['parent-uri']) {
2052 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
2053 WHERE `parent-uri` = '%s' AND `uid` = %d ",
2054 dbesc(datetime_convert()),
2055 dbesc(datetime_convert()),
2056 dbesc($item['parent-uri']),
2057 intval($item['uid'])
2059 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
2060 create_files_from_itemuri($item['parent-uri'], $item['uid']);
2061 delete_thread_uri($item['parent-uri'], $item['uid']);
2062 // ignore the result
2064 // ensure that last-child is set in case the comment that had it just got wiped.
2065 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2066 dbesc(datetime_convert()),
2067 dbesc($item['parent-uri']),
2068 intval($item['uid'])
2070 // who is the last child now?
2071 $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",
2072 dbesc($item['parent-uri']),
2073 intval($item['uid'])
2076 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2082 $drop_id = intval($item['id']);
2084 // send the notification upstream/downstream as the case may be
2086 proc_run(PRIORITY_HIGH,"include/notifier.php", "drop", $drop_id);
2090 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2095 notice( t('Permission denied.') . EOL);
2096 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2103 function first_post_date($uid,$wall = false) {
2104 $r = q("select id, created from item
2105 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
2107 order by created asc limit 1",
2109 intval($wall ? 1 : 0)
2112 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
2113 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
2118 /* modified posted_dates() {below} to arrange the list in years */
2119 function list_post_dates($uid, $wall) {
2120 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2122 $dthen = first_post_date($uid, $wall);
2126 // Set the start and end date to the beginning of the month
2127 $dnow = substr($dnow,0,8).'01';
2128 $dthen = substr($dthen,0,8).'01';
2132 // Starting with the current month, get the first and last days of every
2133 // month down to and including the month of the first post
2134 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2135 $dyear = intval(substr($dnow,0,4));
2136 $dstart = substr($dnow,0,8) . '01';
2137 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2138 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2139 $end_month = datetime_convert('','',$dend,'Y-m-d');
2140 $str = day_translate(datetime_convert('','',$dnow,'F'));
2142 $ret[$dyear] = array();
2143 $ret[$dyear][] = array($str,$end_month,$start_month);
2144 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2149 function posted_dates($uid,$wall) {
2150 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2152 $dthen = first_post_date($uid,$wall);
2156 // Set the start and end date to the beginning of the month
2157 $dnow = substr($dnow,0,8).'01';
2158 $dthen = substr($dthen,0,8).'01';
2161 // Starting with the current month, get the first and last days of every
2162 // month down to and including the month of the first post
2163 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2164 $dstart = substr($dnow,0,8) . '01';
2165 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2166 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2167 $end_month = datetime_convert('','',$dend,'Y-m-d');
2168 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
2169 $ret[] = array($str,$end_month,$start_month);
2170 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2176 function posted_date_widget($url,$uid,$wall) {
2179 if (! feature_enabled($uid,'archives'))
2182 // For former Facebook folks that left because of "timeline"
2184 /* if ($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
2187 $visible_years = get_pconfig($uid,'system','archive_visible_years');
2188 if (! $visible_years)
2191 $ret = list_post_dates($uid,$wall);
2196 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
2197 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
2199 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
2200 '$title' => t('Archives'),
2201 '$size' => $visible_years,
2202 '$cutoff_year' => $cutoff_year,
2203 '$cutoff' => $cutoff,
2206 '$showmore' => t('show more')