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";
153 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
156 if ($no_photos AND ($data["type"] == "photo"))
159 if (sizeof($data["images"]) > 0)
160 $preview = $data["images"][0];
164 // Escape some bad characters
165 $data["url"] = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["url"], ENT_QUOTES, 'UTF-8', false));
166 $data["title"] = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["title"], ENT_QUOTES, 'UTF-8', false));
168 $text = "[attachment type='".$data["type"]."'";
170 if ($data["url"] != "")
171 $text .= " url='".$data["url"]."'";
172 if ($data["title"] != "")
173 $text .= " title='".$data["title"]."'";
174 if (sizeof($data["images"]) > 0) {
175 $preview = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["images"][0]["src"], ENT_QUOTES, 'UTF-8', false));
176 // if the preview picture is larger than 500 pixels then show it in a larger mode
177 // But only, if the picture isn't higher than large (To prevent huge posts)
178 if (($data["images"][0]["width"] >= 500) AND ($data["images"][0]["width"] >= $data["images"][0]["height"]))
179 $text .= " image='".$preview."'";
181 $text .= " preview='".$preview."'";
183 $text .= "]".$data["text"]."[/attachment]";
186 if (isset($data["keywords"]) AND count($data["keywords"])) {
189 foreach ($data["keywords"] AS $keyword) {
190 /// @todo make a positive list of allowed characters
191 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"),
192 array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
193 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
197 return "\n".$text.$hashtags;
200 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
201 require_once("mod/parse_url.php");
203 $data = parseurl_getsiteinfo_cached($url, true);
206 $data["images"][0]["src"] = $photo;
208 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
210 if (!$keywords AND isset($data["keywords"]))
211 unset($data["keywords"]);
213 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
214 $list = explode(",", $keyword_blacklist);
215 foreach ($list AS $keyword) {
216 $keyword = trim($keyword);
217 $index = array_search($keyword, $data["keywords"]);
218 if ($index !== false)
219 unset($data["keywords"][$index]);
226 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
227 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
230 if (isset($data["keywords"]) AND count($data["keywords"])) {
232 foreach ($data["keywords"] AS $keyword) {
233 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
234 array("","", "", "", "", ""), $keyword);
239 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
246 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
247 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
249 $text = add_page_info_data($data);
254 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
256 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
258 $URLSearchString = "^\[\]";
260 // Adding these spaces is a quick hack due to my problems with regular expressions :)
261 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
264 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
266 // Convert urls without bbcode elements
267 if (!$matches AND $texturl) {
268 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
270 // Yeah, a hack. I really hate regular expressions :)
272 $matches[1] = $matches[2];
276 $footer = add_page_info($matches[1], $no_photos);
278 // Remove the link from the body if the link is attached at the end of the post
279 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
280 $removedlink = trim(str_replace($matches[1], "", $body));
281 if (($removedlink == "") OR strstr($body, $removedlink))
282 $body = $removedlink;
284 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
285 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
286 if (($removedlink == "") OR strstr($body, $removedlink))
287 $body = $removedlink;
290 // Add the page information to the bottom
291 if (isset($footer) AND (trim($footer) != ""))
298 * Adds a "lang" specification in a "postopts" element of given $arr,
299 * if possible and not already present.
300 * Expects "body" element to exist in $arr.
302 * @todo Add a parameter to request forcing override
304 function item_add_language_opt(&$arr) {
306 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
308 if ( x($arr, 'postopts') )
310 if ( strstr($arr['postopts'], 'lang=') )
313 /// @TODO Add parameter to request overriding
316 $postopts = $arr['postopts'];
321 require_once('library/langdet/Text/LanguageDetect.php');
322 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
323 $l = new Text_LanguageDetect;
324 //$lng = $l->detectConfidence($naked_body);
325 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
326 $lng = $l->detect($naked_body, 3);
328 if (sizeof($lng) > 0) {
329 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
330 $postopts .= 'lang=';
332 foreach ($lng as $language => $score) {
333 $postopts .= $sep . $language.";".$score;
336 $arr['postopts'] = $postopts;
341 * @brief Creates an unique guid out of a given uri
343 * @param string $uri uri of an item entry
344 * @return string unique guid
346 function uri_to_guid($uri) {
348 // Our regular guid routine is using this kind of prefix as well
349 // We have to avoid that different routines could accidentally create the same value
350 $parsed = parse_url($uri);
351 $guid_prefix = hash("crc32", $parsed["host"]);
353 // Remove the scheme to make sure that "https" and "http" doesn't make a difference
354 unset($parsed["scheme"]);
356 $host_id = implode("/", $parsed);
358 // We could use any hash algorithm since it isn't a security issue
359 $host_hash = hash("ripemd128", $host_id);
361 return $guid_prefix.$host_hash;
364 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
366 // If it is a posting where users should get notifications, then define it as wall posting
369 $arr['type'] = 'wall';
371 $arr['last-child'] = 1;
372 $arr['network'] = NETWORK_DFRN;
375 // If a Diaspora signature structure was passed in, pull it out of the
376 // item array and set it aside for later storage.
379 if (x($arr,'dsprsig')) {
380 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
381 unset($arr['dsprsig']);
384 // Converting the plink
385 if ($arr['network'] == NETWORK_OSTATUS) {
386 if (isset($arr['plink']))
387 $arr['plink'] = ostatus::convert_href($arr['plink']);
388 elseif (isset($arr['uri']))
389 $arr['plink'] = ostatus::convert_href($arr['uri']);
392 if (x($arr, 'gravity'))
393 $arr['gravity'] = intval($arr['gravity']);
394 elseif ($arr['parent-uri'] === $arr['uri'])
396 elseif (activity_match($arr['verb'],ACTIVITY_POST))
399 $arr['gravity'] = 6; // extensible catchall
401 if (! x($arr,'type'))
402 $arr['type'] = 'remote';
406 /* check for create date and expire time */
407 $uid = intval($arr['uid']);
408 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
410 $expire_interval = $r[0]['expire'];
411 if ($expire_interval>0) {
412 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
413 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
414 if ($created_date < $expire_date) {
415 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
421 // Do we already have this item?
422 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
423 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
424 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
425 dbesc(trim($arr['uri'])),
427 dbesc(NETWORK_DIASPORA),
429 dbesc(NETWORK_OSTATUS)
432 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
434 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']);
439 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
440 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
441 //if ((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
442 // $arr['body'] = strip_tags($arr['body']);
444 item_add_language_opt($arr);
448 elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
449 $arr['guid'] = uri_to_guid($arr['plink']);
450 elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
451 $arr['guid'] = uri_to_guid($arr['uri']);
453 $parsed = parse_url($arr["author-link"]);
454 $guid_prefix = hash("crc32", $parsed["host"]);
457 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
458 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
459 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
460 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
461 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
462 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
463 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
464 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
465 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
466 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
467 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
468 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
469 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
470 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
471 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
472 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
473 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
474 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
475 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
476 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
478 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
479 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
480 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
481 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
482 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
483 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
484 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
485 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
486 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
487 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
488 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
489 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
490 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
491 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
492 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
493 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
494 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
495 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
496 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
497 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
498 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
499 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
500 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
501 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
503 // Items cannot be stored before they happen ...
504 if ($arr['created'] > datetime_convert())
505 $arr['created'] = datetime_convert();
507 // We haven't invented time travel by now.
508 if ($arr['edited'] > datetime_convert())
509 $arr['edited'] = datetime_convert();
511 if (($arr['author-link'] == "") AND ($arr['owner-link'] == ""))
512 logger("Both author-link and owner-link are empty. Called by: ".App::callstack(), LOGGER_DEBUG);
514 if ($arr['plink'] == "") {
516 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
519 if ($arr['network'] == "") {
520 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
521 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
522 dbesc(normalise_link($arr['author-link'])),
527 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
528 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
529 dbesc(normalise_link($arr['author-link']))
533 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
534 intval($arr['contact-id']),
539 $arr['network'] = $r[0]["network"];
541 // Fallback to friendica (why is it empty in some cases?)
542 if ($arr['network'] == "")
543 $arr['network'] = NETWORK_DFRN;
545 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
548 // The contact-id should be set before "item_store" was called - but there seems to be some issues
549 if ($arr["contact-id"] == 0) {
550 // First we are looking for a suitable contact that matches with the author of the post
551 // This is done only for comments (See below explanation at "gcontact-id")
552 if ($arr['parent-uri'] != $arr['uri'])
553 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
555 // If not present then maybe the owner was found
556 if ($arr["contact-id"] == 0)
557 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
559 // Still missing? Then use the "self" contact of the current user
560 if ($arr["contact-id"] == 0) {
561 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
563 $arr["contact-id"] = $r[0]["id"];
565 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
568 if ($arr["gcontact-id"] == 0) {
569 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
570 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
571 // On comments the author is the better choice.
572 if ($arr['parent-uri'] === $arr['uri'])
573 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
574 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
576 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
577 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
580 if ($arr["author-id"] == 0)
581 $arr["author-id"] = get_contact($arr["author-link"], 0);
583 if ($arr["owner-id"] == 0)
584 $arr["owner-id"] = get_contact($arr["owner-link"], 0);
586 if ($arr['guid'] != "") {
587 // Checking if there is already an item with the same guid
588 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
589 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
590 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
593 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
598 // Check for hashtags in the body and repair or add hashtag links
599 item_body_set_hashtags($arr);
601 $arr['thr-parent'] = $arr['parent-uri'];
602 if ($arr['parent-uri'] === $arr['uri']) {
605 $allow_cid = $arr['allow_cid'];
606 $allow_gid = $arr['allow_gid'];
607 $deny_cid = $arr['deny_cid'];
608 $deny_gid = $arr['deny_gid'];
609 $notify_type = 'wall-new';
612 // find the parent and snarf the item id and ACLs
613 // and anything else we need to inherit
615 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
616 dbesc($arr['parent-uri']),
622 // is the new message multi-level threaded?
623 // even though we don't support it now, preserve the info
624 // and re-attach to the conversation parent.
626 if ($r[0]['uri'] != $r[0]['parent-uri']) {
627 $arr['parent-uri'] = $r[0]['parent-uri'];
628 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
629 ORDER BY `id` ASC LIMIT 1",
630 dbesc($r[0]['parent-uri']),
631 dbesc($r[0]['parent-uri']),
638 $parent_id = $r[0]['id'];
639 $parent_deleted = $r[0]['deleted'];
640 $allow_cid = $r[0]['allow_cid'];
641 $allow_gid = $r[0]['allow_gid'];
642 $deny_cid = $r[0]['deny_cid'];
643 $deny_gid = $r[0]['deny_gid'];
644 $arr['wall'] = $r[0]['wall'];
645 $notify_type = 'comment-new';
647 // if the parent is private, force privacy for the entire conversation
648 // This differs from the above settings as it subtly allows comments from
649 // email correspondents to be private even if the overall thread is not.
651 if ($r[0]['private'])
652 $arr['private'] = $r[0]['private'];
654 // Edge case. We host a public forum that was originally posted to privately.
655 // The original author commented, but as this is a comment, the permissions
656 // weren't fixed up so it will still show the comment as private unless we fix it here.
658 if ((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
662 // If its a post from myself then tag the thread as "mention"
663 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
664 $u = q("SELECT `nickname` FROM `user` WHERE `uid` = %d", intval($arr['uid']));
667 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
668 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
669 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
670 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
671 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
676 // Allow one to see reply tweets from status.net even when
677 // we don't have or can't see the original post.
680 logger('item_store: $force_parent=true, reply converted to top-level post.');
682 $arr['parent-uri'] = $arr['uri'];
685 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
693 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
695 dbesc($arr['network']),
699 if (dbm::is_result($r)) {
700 logger('duplicated item with the same uri found. '.print_r($arr,true));
704 // On Friendica and Diaspora the GUID is unique
705 if (in_array($arr['network'], array(NETWORK_DFRN, NETWORK_DIASPORA))) {
706 $r = q("SELECT `id` FROM `item` WHERE `guid` = '%s' AND `uid` = %d LIMIT 1",
710 if (dbm::is_result($r)) {
711 logger('duplicated item with the same guid found. '.print_r($arr,true));
715 // Check for an existing post with the same content. There seems to be a problem with OStatus.
716 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
718 dbesc($arr['network']),
719 dbesc($arr['created']),
720 intval($arr['contact-id']),
723 if (dbm::is_result($r)) {
724 logger('duplicated item with the same body found. '.print_r($arr,true));
729 // Is this item available in the global items (with uid=0)?
730 if ($arr["uid"] == 0) {
731 $arr["global"] = true;
733 // Set the global flag on all items if this was a global item entry
734 q("UPDATE `item` SET `global` = 1 WHERE `uri` = '%s'", dbesc($arr["uri"]));
736 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `uri` = '%s'", dbesc($arr["uri"]));
738 $arr["global"] = (count($isglobal) > 0);
742 if (strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
745 $private = $arr['private'];
747 $arr["allow_cid"] = $allow_cid;
748 $arr["allow_gid"] = $allow_gid;
749 $arr["deny_cid"] = $deny_cid;
750 $arr["deny_gid"] = $deny_gid;
751 $arr["private"] = $private;
752 $arr["deleted"] = $parent_deleted;
754 // Fill the cache field
755 put_item_in_cache($arr);
758 call_hooks('post_local',$arr);
760 call_hooks('post_remote',$arr);
762 if (x($arr,'cancel')) {
763 logger('item_store: post cancelled by plugin.');
767 // Check for already added items.
768 // There is a timing issue here that sometimes creates double postings.
769 // An unique index would help - but the limitations of MySQL (maximum size of index values) prevent this.
770 if ($arr["uid"] == 0) {
771 $r = qu("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = 0 LIMIT 1", dbesc(trim($arr['uri'])));
772 if (dbm::is_result($r)) {
773 logger('Global item already stored. URI: '.$arr['uri'].' on network '.$arr['network'], LOGGER_DEBUG);
778 // Store the unescaped version
783 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
786 q("START TRANSACTION;");
788 $r = dbq("INSERT INTO `item` (`"
789 . implode("`, `", array_keys($arr))
791 . implode("', '", array_values($arr))
797 // When the item was successfully stored we fetch the ID of the item.
798 if (dbm::is_result($r)) {
799 $r = q("SELECT LAST_INSERT_ID() AS `item-id`");
800 if (dbm::is_result($r)) {
801 $current_post = $r[0]['item-id'];
803 // This shouldn't happen
807 // This can happen - for example - if there are locking timeouts.
808 logger("Item wasn't stored - we quit here.");
813 if ($current_post == 0) {
814 // This is one of these error messages that never should occur.
815 logger("couldn't find created item - we better quit now.");
820 // How much entries have we created?
821 // We wouldn't need this query when we could use an unique index - but MySQL has length problems with them.
822 $r = q("SELECT COUNT(*) AS `entries` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s'",
825 dbesc($arr['network'])
828 if (!dbm::is_result($r)) {
829 // This shouldn't happen, since COUNT always works when the database connection is there.
830 logger("We couldn't count the stored entries. Very strange ...");
835 if ($r[0]["entries"] > 1) {
836 // There are duplicates. We delete our just created entry.
837 logger('Duplicated post occurred. uri = '.$arr['uri'].' uid = '.$arr['uid']);
839 // Yes, we could do a rollback here - but we are having many users with MyISAM.
840 q("DELETE FROM `item` WHERE `id` = %d", intval($current_post));
843 } elseif ($r[0]["entries"] == 0) {
844 // This really should never happen since we quit earlier if there were problems.
845 logger("Something is terribly wrong. We haven't found our created entry.");
850 logger('item_store: created item '.$current_post);
851 item_set_last_item($arr);
853 if (!$parent_id || ($arr['parent-uri'] === $arr['uri']))
854 $parent_id = $current_post;
857 $r = q("UPDATE `item` SET `parent` = %d WHERE `id` = %d",
859 intval($current_post)
862 $arr['id'] = $current_post;
863 $arr['parent'] = $parent_id;
865 // update the commented timestamp on the parent
866 // Only update "commented" if it is really a comment
867 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
868 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
869 dbesc(datetime_convert()),
870 dbesc(datetime_convert()),
874 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
875 dbesc(datetime_convert()),
881 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
882 // We can check for this condition when we decode and encode the stuff again.
883 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
884 $dsprsig->signature = base64_decode($dsprsig->signature);
885 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
888 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
889 intval($current_post),
890 dbesc($dsprsig->signed_text),
891 dbesc($dsprsig->signature),
892 dbesc($dsprsig->signer)
896 $deleted = tag_deliver($arr['uid'],$current_post);
898 // current post can be deleted if is for a community page and no mention are
900 if (!$deleted AND !$dontcache) {
902 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
903 if (count($r) == 1) {
905 call_hooks('post_local_end', $r[0]);
907 call_hooks('post_remote_end', $r[0]);
909 logger('item_store: new item not found in DB, id ' . $current_post);
912 if ($arr['parent-uri'] === $arr['uri']) {
913 add_thread($current_post);
915 update_thread($parent_id);
920 // Due to deadlock issues with the "term" table we are doing these steps after the commit.
921 // This is not perfect - but a workable solution until we found the reason for the problem.
922 create_tags_from_item($current_post);
923 create_files_from_item($current_post);
926 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
927 * It is done after the transaction to avoid dead locks.
930 if ($arr['last-child']) {
931 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
934 intval($current_post)
938 if ($arr['parent-uri'] === $arr['uri']) {
939 add_shadow_thread($current_post);
941 add_shadow_entry($current_post);
944 check_item_notification($current_post, $uid);
947 proc_run(PRIORITY_HIGH, "include/notifier.php", $notify_type, $current_post);
949 return $current_post;
953 * @brief Set "success_update" and "last-item" to the date of the last time we heard from this contact
955 * This can be used to filter for inactive contacts.
956 * Only do this for public postings to avoid privacy problems, since poco data is public.
957 * Don't set this value if it isn't from the owner (could be an author that we don't know)
959 * @param array $arr Contains the just posted item record
961 function item_set_last_item($arr) {
963 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
965 // Is it a forum? Then we don't care about the rules from above
966 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
967 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
968 intval($arr['contact-id']));
975 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
976 dbesc($arr['received']),
977 dbesc($arr['received']),
978 intval($arr['contact-id'])
981 // Now do the same for the system wide contacts with uid=0
982 if (!$arr['private']) {
983 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
984 dbesc($arr['received']),
985 dbesc($arr['received']),
986 intval($arr['owner-id'])
989 if ($arr['owner-id'] != $arr['author-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['author-id'])
999 function item_body_set_hashtags(&$item) {
1001 $tags = get_tags($item["body"]);
1007 // This sorting is important when there are hashtags that are part of other hashtags
1008 // Otherwise there could be problems with hashtags like #test and #test2
1013 $URLSearchString = "^\[\]";
1015 // All hashtags should point to the home server
1016 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1017 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1019 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1020 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1022 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1023 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1025 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1028 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1030 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1033 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1035 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1038 // Repair recursive urls
1039 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1040 "#$2", $item["body"]);
1043 foreach($tags as $tag) {
1044 if (strpos($tag,'#') !== 0)
1047 if (strpos($tag,'[url='))
1050 $basetag = str_replace('_',' ',substr($tag,1));
1052 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1054 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1056 if (!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1057 if (strlen($item["tag"]))
1058 $item["tag"] = ','.$item["tag"];
1059 $item["tag"] = $newtag.$item["tag"];
1063 // Convert back the masked hashtags
1064 $item["body"] = str_replace("#", "#", $item["body"]);
1067 function get_item_guid($id) {
1068 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1070 return($r[0]["guid"]);
1075 function get_item_id($guid, $uid = 0) {
1081 $uid == local_user();
1083 // Does the given user have this item?
1085 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1086 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1087 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1090 $nick = $r[0]["nickname"];
1094 // Or is it anywhere on the server?
1096 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1097 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1098 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1099 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1100 AND `item`.`private` = 0 AND `item`.`wall` = 1
1101 AND `item`.`guid` = '%s'", dbesc($guid));
1104 $nick = $r[0]["nickname"];
1107 return(array("nick" => $nick, "id" => $id));
1111 function get_item_contact($item,$contacts) {
1112 if (! count($contacts) || (! is_array($item)))
1114 foreach($contacts as $contact) {
1115 if ($contact['id'] == $item['contact-id']) {
1117 break; // NOTREACHED
1124 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1126 * @param int $item_id
1127 * @return bool true if item was deleted, else false
1129 function tag_deliver($uid,$item_id) {
1137 $u = q("select * from user where uid = %d limit 1",
1143 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1144 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1147 $i = q("select * from item where id = %d and uid = %d limit 1",
1156 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1158 // Diaspora uses their own hardwired link URL in @-tags
1159 // instead of the one we supply with webfinger
1161 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1163 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1165 foreach($matches as $mtch) {
1166 if (link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1168 logger('tag_deliver: mention found: ' . $mtch[2]);
1174 if ( ($community_page || $prvgroup) &&
1175 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1176 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1178 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1179 q("DELETE FROM item WHERE id = %d and uid = %d",
1188 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1190 call_hooks('tagged', $arr);
1192 if ((! $community_page) && (! $prvgroup))
1196 // tgroup delivery - setup a second delivery chain
1197 // prevent delivery looping - only proceed
1198 // if the message originated elsewhere and is a top-level post
1200 if (($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1203 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1206 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1207 intval($u[0]['uid'])
1212 // also reset all the privacy bits to the forum default permissions
1214 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1216 $forum_mode = (($prvgroup) ? 2 : 1);
1218 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1219 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1220 intval($forum_mode),
1221 dbesc($c[0]['name']),
1222 dbesc($c[0]['url']),
1223 dbesc($c[0]['thumb']),
1225 dbesc($u[0]['allow_cid']),
1226 dbesc($u[0]['allow_gid']),
1227 dbesc($u[0]['deny_cid']),
1228 dbesc($u[0]['deny_gid']),
1231 update_thread($item_id);
1233 proc_run(PRIORITY_HIGH,'include/notifier.php', 'tgroup', $item_id);
1239 function tgroup_check($uid,$item) {
1245 // check that the message originated elsewhere and is a top-level post
1247 if (($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1251 $u = q("select * from user where uid = %d limit 1",
1257 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1258 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1261 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1263 // Diaspora uses their own hardwired link URL in @-tags
1264 // instead of the one we supply with webfinger
1266 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1268 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1270 foreach($matches as $mtch) {
1271 if (link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1273 logger('tgroup_check: mention found: ' . $mtch[2]);
1281 if ((! $community_page) && (! $prvgroup))
1288 This function returns true if $update has an edited timestamp newer
1289 than $existing, i.e. $update contains new data which should override
1290 what's already there. If there is no timestamp yet, the update is
1291 assumed to be newer. If the update has no timestamp, the existing
1292 item is assumed to be up-to-date. If the timestamps are equal it
1293 assumes the update has been seen before and should be ignored.
1295 function edited_timestamp_is_newer($existing, $update) {
1296 if (!x($existing,'edited') || !$existing['edited']) {
1299 if (!x($update,'edited') || !$update['edited']) {
1302 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1303 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1304 return (strcmp($existing_edited, $update_edited) < 0);
1309 * consume_feed - process atom feed and update anything/everything we might need to update
1311 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1313 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1314 * It is this person's stuff that is going to be updated.
1315 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1316 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1317 * have a contact record.
1318 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1319 * might not) try and subscribe to it.
1320 * $datedir sorts in reverse order
1321 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1322 * imported prior to its children being seen in the stream unless we are certain
1323 * of how the feed is arranged/ordered.
1324 * With $pass = 1, we only pull parent items out of the stream.
1325 * With $pass = 2, we only pull children (comments/likes).
1327 * So running this twice, first with pass 1 and then with pass 2 will do the right
1328 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1329 * model where comments can have sub-threads. That would require some massive sorting
1330 * to get all the feed items into a mostly linear ordering, and might still require
1334 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1335 if ($contact['network'] === NETWORK_OSTATUS) {
1337 // Test - remove before flight
1338 //$tempfile = tempnam(get_temppath(), "ostatus2");
1339 //file_put_contents($tempfile, $xml);
1340 logger("Consume OStatus messages ", LOGGER_DEBUG);
1341 ostatus::import($xml,$importer,$contact, $hub);
1346 if ($contact['network'] === NETWORK_FEED) {
1348 logger("Consume feeds", LOGGER_DEBUG);
1349 feed_import($xml,$importer,$contact, $hub);
1354 if ($contact['network'] === NETWORK_DFRN) {
1355 logger("Consume DFRN messages", LOGGER_DEBUG);
1357 $r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
1358 `contact`.`pubkey` AS `cpubkey`,
1359 `contact`.`prvkey` AS `cprvkey`,
1360 `contact`.`thumb` AS `thumb`,
1361 `contact`.`url` as `url`,
1362 `contact`.`name` as `senderName`,
1365 LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
1366 WHERE `contact`.`id` = %d AND `user`.`uid` = %d",
1367 dbesc($contact["id"]), dbesc($importer["uid"])
1370 logger("Now import the DFRN feed");
1371 dfrn::import($xml,$r[0], true);
1377 function item_is_remote_self($contact, &$datarray) {
1380 if (!$contact['remote_self'])
1383 // Prevent the forwarding of posts that are forwarded
1384 if ($datarray["extid"] == NETWORK_DFRN)
1387 // Prevent to forward already forwarded posts
1388 if ($datarray["app"] == $a->get_hostname())
1391 // Only forward posts
1392 if ($datarray["verb"] != ACTIVITY_POST)
1395 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
1398 $datarray2 = $datarray;
1399 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
1400 if ($contact['remote_self'] == 2) {
1401 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
1402 intval($contact['uid']));
1404 $datarray['contact-id'] = $r[0]["id"];
1406 $datarray['owner-name'] = $r[0]["name"];
1407 $datarray['owner-link'] = $r[0]["url"];
1408 $datarray['owner-avatar'] = $r[0]["thumb"];
1410 $datarray['author-name'] = $datarray['owner-name'];
1411 $datarray['author-link'] = $datarray['owner-link'];
1412 $datarray['author-avatar'] = $datarray['owner-avatar'];
1415 if ($contact['network'] != NETWORK_FEED) {
1416 $datarray["guid"] = get_guid(32);
1417 unset($datarray["plink"]);
1418 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
1419 $datarray["parent-uri"] = $datarray["uri"];
1420 $datarray["extid"] = $contact['network'];
1421 $urlpart = parse_url($datarray2['author-link']);
1422 $datarray["app"] = $urlpart["host"];
1424 $datarray['private'] = 0;
1427 if ($contact['network'] != NETWORK_FEED) {
1428 // Store the original post
1429 $r = item_store($datarray2, false, false);
1430 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
1432 $datarray["app"] = "Feed";
1437 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
1438 $url = notags(trim($datarray['author-link']));
1439 $name = notags(trim($datarray['author-name']));
1440 $photo = notags(trim($datarray['author-avatar']));
1442 if (is_object($item)) {
1443 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
1444 if ($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
1445 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
1449 if (is_array($contact)) {
1450 if (($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
1451 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
1452 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
1453 intval(CONTACT_IS_FRIEND),
1454 intval($contact['id']),
1455 intval($importer['uid'])
1458 // send email notification to owner?
1461 // create contact record
1463 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
1464 `blocked`, `readonly`, `pending`, `writable`)
1465 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
1466 intval($importer['uid']),
1467 dbesc(datetime_convert()),
1469 dbesc(normalise_link($url)),
1473 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
1474 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
1476 $r = q("SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
1477 intval($importer['uid']),
1481 $contact_record = $r[0];
1482 update_contact_avatar($photo, $importer["uid"], $contact_record["id"], true);
1486 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
1487 intval($importer['uid'])
1490 if (count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1492 // create notification
1493 $hash = random_string();
1495 if (is_array($contact_record)) {
1496 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
1497 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
1498 intval($importer['uid']),
1499 intval($contact_record['id']),
1501 dbesc(datetime_convert())
1505 $def_gid = get_default_group($importer['uid'], $contact_record["network"]);
1507 if (intval($def_gid))
1508 group_add_member($importer['uid'],'',$contact_record['id'],$def_gid);
1510 if (($r[0]['notify-flags'] & NOTIFY_INTRO) &&
1511 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
1514 'type' => NOTIFY_INTRO,
1515 'notify_flags' => $r[0]['notify-flags'],
1516 'language' => $r[0]['language'],
1517 'to_name' => $r[0]['username'],
1518 'to_email' => $r[0]['email'],
1519 'uid' => $r[0]['uid'],
1520 'link' => $a->get_baseurl() . '/notifications/intro',
1521 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
1522 'source_link' => $contact_record['url'],
1523 'source_photo' => $contact_record['photo'],
1524 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
1529 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1530 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
1531 intval($importer['uid']),
1539 function lose_follower($importer,$contact,$datarray = array(),$item = "") {
1541 if (($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
1542 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1543 intval(CONTACT_IS_SHARING),
1544 intval($contact['id'])
1547 contact_remove($contact['id']);
1551 function lose_sharer($importer,$contact,$datarray = array(),$item = "") {
1553 if (($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
1554 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1555 intval(CONTACT_IS_FOLLOWER),
1556 intval($contact['id'])
1559 contact_remove($contact['id']);
1563 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
1567 if (is_array($importer)) {
1568 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
1569 intval($importer['uid'])
1573 // Diaspora has different message-ids in feeds than they do
1574 // through the direct Diaspora protocol. If we try and use
1575 // the feed, we'll get duplicates. So don't.
1577 if ((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
1580 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
1582 // Use a single verify token, even if multiple hubs
1584 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
1586 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
1588 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
1590 if (!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
1591 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
1592 dbesc($verify_token),
1593 intval($contact['id'])
1597 post_url($url,$params);
1599 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
1605 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
1607 if (get_config('system','disable_embedded'))
1612 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
1613 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
1618 $img_start = strpos($orig_body, '[img');
1619 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1620 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1621 while( ($img_st_close !== false) && ($img_len !== false) ) {
1623 $img_st_close++; // make it point to AFTER the closing bracket
1624 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
1626 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
1629 if (stristr($image , $site . '/photo/')) {
1630 // Only embed locally hosted photos
1632 $i = basename($image);
1633 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
1634 $x = strpos($i,'-');
1637 $res = substr($i,$x+1);
1638 $i = substr($i,0,$x);
1639 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
1646 // Check to see if we should replace this photo link with an embedded image
1647 // 1. No need to do so if the photo is public
1648 // 2. If there's a contact-id provided, see if they're in the access list
1649 // for the photo. If so, embed it.
1650 // 3. Otherwise, if we have an item, see if the item permissions match the photo
1651 // permissions, regardless of order but first check to see if they're an exact
1652 // match to save some processing overhead.
1654 if (has_permissions($r[0])) {
1656 $recips = enumerate_permissions($r[0]);
1657 if (in_array($cid, $recips)) {
1661 if (compare_permissions($item,$r[0]))
1666 $data = $r[0]['data'];
1667 $type = $r[0]['type'];
1669 // If a custom width and height were specified, apply before embedding
1670 if (preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
1671 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
1673 $width = intval($match[1]);
1674 $height = intval($match[2]);
1676 $ph = new Photo($data, $type);
1677 if ($ph->is_valid()) {
1678 $ph->scaleImage(max($width, $height));
1679 $data = $ph->imageString();
1680 $type = $ph->getType();
1684 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
1685 $image = 'data:' . $type . ';base64,' . base64_encode($data);
1686 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
1692 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
1693 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
1694 if ($orig_body === false)
1697 $img_start = strpos($orig_body, '[img');
1698 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1699 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1702 $new_body = $new_body . $orig_body;
1707 function has_permissions($obj) {
1708 if (($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
1713 function compare_permissions($obj1,$obj2) {
1714 // first part is easy. Check that these are exactly the same.
1715 if (($obj1['allow_cid'] == $obj2['allow_cid'])
1716 && ($obj1['allow_gid'] == $obj2['allow_gid'])
1717 && ($obj1['deny_cid'] == $obj2['deny_cid'])
1718 && ($obj1['deny_gid'] == $obj2['deny_gid']))
1721 // This is harder. Parse all the permissions and compare the resulting set.
1723 $recipients1 = enumerate_permissions($obj1);
1724 $recipients2 = enumerate_permissions($obj2);
1727 if ($recipients1 == $recipients2)
1732 // returns an array of contact-ids that are allowed to see this object
1734 function enumerate_permissions($obj) {
1735 $allow_people = expand_acl($obj['allow_cid']);
1736 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
1737 $deny_people = expand_acl($obj['deny_cid']);
1738 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
1739 $recipients = array_unique(array_merge($allow_people,$allow_groups));
1740 $deny = array_unique(array_merge($deny_people,$deny_groups));
1741 $recipients = array_diff($recipients,$deny);
1745 function item_getfeedtags($item) {
1748 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1750 for($x = 0; $x < $cnt; $x ++) {
1751 if ($matches[1][$x])
1752 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
1756 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1758 for($x = 0; $x < $cnt; $x ++) {
1759 if ($matches[1][$x])
1760 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
1766 function item_expire($uid, $days, $network = "", $force = false) {
1768 if ((! $uid) || ($days < 1))
1771 // $expire_network_only = save your own wall posts
1772 // and just expire conversations started by others
1774 $expire_network_only = get_pconfig($uid,'expire','network_only');
1775 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
1777 if ($network != "") {
1778 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
1779 // There is an index "uid_network_received" but not "uid_network_created"
1780 // This avoids the creation of another index just for one purpose.
1781 // And it doesn't really matter wether to look at "received" or "created"
1782 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1784 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1786 $r = q("SELECT `file`, `resource-id`, `starred`, `type`, `id` FROM `item`
1787 WHERE `uid` = %d $range
1798 $expire_items = get_pconfig($uid, 'expire','items');
1799 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
1801 // Forcing expiring of items - but not notes and marked items
1803 $expire_items = true;
1805 $expire_notes = get_pconfig($uid, 'expire','notes');
1806 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
1808 $expire_starred = get_pconfig($uid, 'expire','starred');
1809 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
1811 $expire_photos = get_pconfig($uid, 'expire','photos');
1812 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
1814 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
1816 foreach($r as $item) {
1818 // don't expire filed items
1820 if (strpos($item['file'],'[') !== false)
1823 // Only expire posts, not photos and photo comments
1825 if ($expire_photos==0 && strlen($item['resource-id']))
1827 if ($expire_starred==0 && intval($item['starred']))
1829 if ($expire_notes==0 && $item['type']=='note')
1831 if ($expire_items==0 && $item['type']!='note')
1834 drop_item($item['id'],false);
1837 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1842 function drop_items($items) {
1845 if (! local_user() && ! remote_user())
1848 if (count($items)) {
1849 foreach($items as $item) {
1850 $owner = drop_item($item,false);
1851 if ($owner && ! $uid)
1856 // multiple threads may have been deleted, send an expire notification
1859 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1863 function drop_item($id,$interactive = true) {
1867 // locate item to be deleted
1869 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
1876 notice( t('Item not found.') . EOL);
1877 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1882 $owner = $item['uid'];
1886 // check if logged in user is either the author or owner of this item
1888 if (is_array($_SESSION['remote'])) {
1889 foreach($_SESSION['remote'] as $visitor) {
1890 if ($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
1891 $cid = $visitor['cid'];
1898 if ((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
1900 // Check if we should do HTML-based delete confirmation
1901 if ($_REQUEST['confirm']) {
1902 // <form> can't take arguments in its "action" parameter
1903 // so add any arguments as hidden inputs
1904 $query = explode_querystring($a->query_string);
1906 foreach($query['args'] as $arg) {
1907 if (strpos($arg, 'confirm=') === false) {
1908 $arg_parts = explode('=', $arg);
1909 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
1913 return replace_macros(get_markup_template('confirm.tpl'), array(
1915 '$message' => t('Do you really want to delete this item?'),
1916 '$extra_inputs' => $inputs,
1917 '$confirm' => t('Yes'),
1918 '$confirm_url' => $query['base'],
1919 '$confirm_name' => 'confirmed',
1920 '$cancel' => t('Cancel'),
1923 // Now check how the user responded to the confirmation query
1924 if ($_REQUEST['canceled']) {
1925 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1928 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
1931 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
1932 dbesc(datetime_convert()),
1933 dbesc(datetime_convert()),
1936 create_tags_from_item($item['id']);
1937 create_files_from_item($item['id']);
1938 delete_thread($item['id'], $item['parent-uri']);
1940 // clean up categories and tags so they don't end up as orphans
1943 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1945 foreach($matches as $mtch) {
1946 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
1952 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1954 foreach($matches as $mtch) {
1955 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
1959 // If item is a link to a photo resource, nuke all the associated photos
1960 // (visitors will not have photo resources)
1961 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
1962 // generate a resource-id and therefore aren't intimately linked to the item.
1964 if (strlen($item['resource-id'])) {
1965 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
1966 dbesc($item['resource-id']),
1967 intval($item['uid'])
1969 // ignore the result
1972 // If item is a link to an event, nuke the event record.
1974 if (intval($item['event-id'])) {
1975 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
1976 intval($item['event-id']),
1977 intval($item['uid'])
1979 // ignore the result
1982 // If item has attachments, drop them
1984 foreach(explode(",",$item['attach']) as $attach){
1985 preg_match("|attach/(\d+)|", $attach, $matches);
1986 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
1987 intval($matches[1]),
1990 // ignore the result
1994 // clean up item_id and sign meta-data tables
1997 // Old code - caused very long queries and warning entries in the mysql logfiles:
1999 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
2000 intval($item['id']),
2001 intval($item['uid'])
2004 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
2005 intval($item['id']),
2006 intval($item['uid'])
2010 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
2012 // Creating list of parents
2013 $r = q("select id from item where parent = %d and uid = %d",
2014 intval($item['id']),
2015 intval($item['uid'])
2020 foreach ($r AS $row) {
2021 if ($parentid != "")
2024 $parentid .= $row["id"];
2028 if ($parentid != "") {
2029 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
2031 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
2034 // If it's the parent of a comment thread, kill all the kids
2036 if ($item['uri'] == $item['parent-uri']) {
2037 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
2038 WHERE `parent-uri` = '%s' AND `uid` = %d ",
2039 dbesc(datetime_convert()),
2040 dbesc(datetime_convert()),
2041 dbesc($item['parent-uri']),
2042 intval($item['uid'])
2044 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
2045 create_files_from_itemuri($item['parent-uri'], $item['uid']);
2046 delete_thread_uri($item['parent-uri'], $item['uid']);
2047 // ignore the result
2049 // ensure that last-child is set in case the comment that had it just got wiped.
2050 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2051 dbesc(datetime_convert()),
2052 dbesc($item['parent-uri']),
2053 intval($item['uid'])
2055 // who is the last child now?
2056 $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",
2057 dbesc($item['parent-uri']),
2058 intval($item['uid'])
2061 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2067 $drop_id = intval($item['id']);
2069 // send the notification upstream/downstream as the case may be
2071 proc_run(PRIORITY_HIGH,"include/notifier.php", "drop", $drop_id);
2075 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2080 notice( t('Permission denied.') . EOL);
2081 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2088 function first_post_date($uid,$wall = false) {
2089 $r = q("select id, created from item
2090 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
2092 order by created asc limit 1",
2094 intval($wall ? 1 : 0)
2097 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
2098 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
2103 /* modified posted_dates() {below} to arrange the list in years */
2104 function list_post_dates($uid, $wall) {
2105 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2107 $dthen = first_post_date($uid, $wall);
2111 // Set the start and end date to the beginning of the month
2112 $dnow = substr($dnow,0,8).'01';
2113 $dthen = substr($dthen,0,8).'01';
2117 // Starting with the current month, get the first and last days of every
2118 // month down to and including the month of the first post
2119 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2120 $dyear = intval(substr($dnow,0,4));
2121 $dstart = substr($dnow,0,8) . '01';
2122 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2123 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2124 $end_month = datetime_convert('','',$dend,'Y-m-d');
2125 $str = day_translate(datetime_convert('','',$dnow,'F'));
2127 $ret[$dyear] = array();
2128 $ret[$dyear][] = array($str,$end_month,$start_month);
2129 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2134 function posted_dates($uid,$wall) {
2135 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2137 $dthen = first_post_date($uid,$wall);
2141 // Set the start and end date to the beginning of the month
2142 $dnow = substr($dnow,0,8).'01';
2143 $dthen = substr($dthen,0,8).'01';
2146 // Starting with the current month, get the first and last days of every
2147 // month down to and including the month of the first post
2148 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2149 $dstart = substr($dnow,0,8) . '01';
2150 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2151 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2152 $end_month = datetime_convert('','',$dend,'Y-m-d');
2153 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
2154 $ret[] = array($str,$end_month,$start_month);
2155 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2161 function posted_date_widget($url,$uid,$wall) {
2164 if (! feature_enabled($uid,'archives'))
2167 // For former Facebook folks that left because of "timeline"
2169 /* if ($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
2172 $visible_years = get_pconfig($uid,'system','archive_visible_years');
2173 if (! $visible_years)
2176 $ret = list_post_dates($uid,$wall);
2181 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
2182 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
2184 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
2185 '$title' => t('Archives'),
2186 '$size' => $visible_years,
2187 '$cutoff_year' => $cutoff_year,
2188 '$cutoff' => $cutoff,
2191 '$showmore' => t('show more')