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"] == $data["url"])) {
157 if ($no_photos AND ($data["type"] == "photo"))
160 if (sizeof($data["images"]) > 0)
161 $preview = $data["images"][0];
165 // Escape some bad characters
166 $data["url"] = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["url"], ENT_QUOTES, 'UTF-8', false));
167 $data["title"] = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["title"], ENT_QUOTES, 'UTF-8', false));
169 $text = "[attachment type='".$data["type"]."'";
171 if ($data["text"] == "") {
172 $data["text"] = $data["title"];
175 if ($data["text"] == "") {
176 $data["text"] = $data["url"];
179 if ($data["url"] != "")
180 $text .= " url='".$data["url"]."'";
181 if ($data["title"] != "")
182 $text .= " title='".$data["title"]."'";
183 if (sizeof($data["images"]) > 0) {
184 $preview = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["images"][0]["src"], ENT_QUOTES, 'UTF-8', false));
185 // if the preview picture is larger than 500 pixels then show it in a larger mode
186 // But only, if the picture isn't higher than large (To prevent huge posts)
187 if (($data["images"][0]["width"] >= 500) AND ($data["images"][0]["width"] >= $data["images"][0]["height"]))
188 $text .= " image='".$preview."'";
190 $text .= " preview='".$preview."'";
192 $text .= "]".$data["text"]."[/attachment]";
195 if (isset($data["keywords"]) AND count($data["keywords"])) {
198 foreach ($data["keywords"] AS $keyword) {
199 /// @todo make a positive list of allowed characters
200 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"),
201 array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
202 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
206 return "\n".$text.$hashtags;
209 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
210 require_once("mod/parse_url.php");
212 $data = parseurl_getsiteinfo_cached($url, true);
215 $data["images"][0]["src"] = $photo;
217 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
219 if (!$keywords AND isset($data["keywords"]))
220 unset($data["keywords"]);
222 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
223 $list = explode(",", $keyword_blacklist);
224 foreach ($list AS $keyword) {
225 $keyword = trim($keyword);
226 $index = array_search($keyword, $data["keywords"]);
227 if ($index !== false)
228 unset($data["keywords"][$index]);
235 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
236 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
239 if (isset($data["keywords"]) AND count($data["keywords"])) {
241 foreach ($data["keywords"] AS $keyword) {
242 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
243 array("","", "", "", "", ""), $keyword);
248 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
255 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
256 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
258 $text = add_page_info_data($data);
263 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
265 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
267 $URLSearchString = "^\[\]";
269 // Adding these spaces is a quick hack due to my problems with regular expressions :)
270 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
273 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
275 // Convert urls without bbcode elements
276 if (!$matches AND $texturl) {
277 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
279 // Yeah, a hack. I really hate regular expressions :)
281 $matches[1] = $matches[2];
285 $footer = add_page_info($matches[1], $no_photos);
287 // Remove the link from the body if the link is attached at the end of the post
288 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
289 $removedlink = trim(str_replace($matches[1], "", $body));
290 if (($removedlink == "") OR strstr($body, $removedlink))
291 $body = $removedlink;
293 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
294 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
295 if (($removedlink == "") OR strstr($body, $removedlink))
296 $body = $removedlink;
299 // Add the page information to the bottom
300 if (isset($footer) AND (trim($footer) != ""))
307 * Adds a "lang" specification in a "postopts" element of given $arr,
308 * if possible and not already present.
309 * Expects "body" element to exist in $arr.
311 * @todo Add a parameter to request forcing override
313 function item_add_language_opt(&$arr) {
315 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
317 if ( x($arr, 'postopts') )
319 if ( strstr($arr['postopts'], 'lang=') )
322 /// @TODO Add parameter to request overriding
325 $postopts = $arr['postopts'];
330 require_once('library/langdet/Text/LanguageDetect.php');
331 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
332 $l = new Text_LanguageDetect;
333 //$lng = $l->detectConfidence($naked_body);
334 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
335 $lng = $l->detect($naked_body, 3);
337 if (sizeof($lng) > 0) {
338 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
339 $postopts .= 'lang=';
341 foreach ($lng as $language => $score) {
342 $postopts .= $sep . $language.";".$score;
345 $arr['postopts'] = $postopts;
350 * @brief Creates an unique guid out of a given uri
352 * @param string $uri uri of an item entry
353 * @return string unique guid
355 function uri_to_guid($uri) {
357 // Our regular guid routine is using this kind of prefix as well
358 // We have to avoid that different routines could accidentally create the same value
359 $parsed = parse_url($uri);
360 $guid_prefix = hash("crc32", $parsed["host"]);
362 // Remove the scheme to make sure that "https" and "http" doesn't make a difference
363 unset($parsed["scheme"]);
365 $host_id = implode("/", $parsed);
367 // We could use any hash algorithm since it isn't a security issue
368 $host_hash = hash("ripemd128", $host_id);
370 return $guid_prefix.$host_hash;
373 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
375 // If it is a posting where users should get notifications, then define it as wall posting
378 $arr['type'] = 'wall';
380 $arr['last-child'] = 1;
381 $arr['network'] = NETWORK_DFRN;
384 // If a Diaspora signature structure was passed in, pull it out of the
385 // item array and set it aside for later storage.
388 if (x($arr,'dsprsig')) {
389 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
390 unset($arr['dsprsig']);
393 // Converting the plink
394 if ($arr['network'] == NETWORK_OSTATUS) {
395 if (isset($arr['plink']))
396 $arr['plink'] = ostatus::convert_href($arr['plink']);
397 elseif (isset($arr['uri']))
398 $arr['plink'] = ostatus::convert_href($arr['uri']);
401 if (x($arr, 'gravity'))
402 $arr['gravity'] = intval($arr['gravity']);
403 elseif ($arr['parent-uri'] === $arr['uri'])
405 elseif (activity_match($arr['verb'],ACTIVITY_POST))
408 $arr['gravity'] = 6; // extensible catchall
410 if (! x($arr,'type'))
411 $arr['type'] = 'remote';
415 /* check for create date and expire time */
416 $uid = intval($arr['uid']);
417 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
419 $expire_interval = $r[0]['expire'];
420 if ($expire_interval>0) {
421 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
422 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
423 if ($created_date < $expire_date) {
424 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
430 // Do we already have this item?
431 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
432 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
433 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
434 dbesc(trim($arr['uri'])),
436 dbesc(NETWORK_DIASPORA),
438 dbesc(NETWORK_OSTATUS)
441 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
443 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']);
448 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
449 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
450 //if ((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
451 // $arr['body'] = strip_tags($arr['body']);
453 item_add_language_opt($arr);
457 elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
458 $arr['guid'] = uri_to_guid($arr['plink']);
459 elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
460 $arr['guid'] = uri_to_guid($arr['uri']);
462 $parsed = parse_url($arr["author-link"]);
463 $guid_prefix = hash("crc32", $parsed["host"]);
466 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
467 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
468 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
469 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
470 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
471 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
472 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
473 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
474 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
475 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
476 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
477 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
478 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
479 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
480 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
481 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
482 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
483 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
484 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
485 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
487 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
488 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
489 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
490 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
491 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
492 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
493 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
494 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
495 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
496 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
497 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
498 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
499 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
500 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
501 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
502 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
503 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
504 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
505 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
506 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
507 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
508 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
509 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
510 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
512 // Items cannot be stored before they happen ...
513 if ($arr['created'] > datetime_convert())
514 $arr['created'] = datetime_convert();
516 // We haven't invented time travel by now.
517 if ($arr['edited'] > datetime_convert())
518 $arr['edited'] = datetime_convert();
520 if (($arr['author-link'] == "") AND ($arr['owner-link'] == ""))
521 logger("Both author-link and owner-link are empty. Called by: ".App::callstack(), LOGGER_DEBUG);
523 if ($arr['plink'] == "") {
525 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
528 if ($arr['network'] == "") {
529 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
530 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
531 dbesc(normalise_link($arr['author-link'])),
536 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
537 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
538 dbesc(normalise_link($arr['author-link']))
542 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
543 intval($arr['contact-id']),
548 $arr['network'] = $r[0]["network"];
550 // Fallback to friendica (why is it empty in some cases?)
551 if ($arr['network'] == "")
552 $arr['network'] = NETWORK_DFRN;
554 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
557 // The contact-id should be set before "item_store" was called - but there seems to be some issues
558 if ($arr["contact-id"] == 0) {
559 // First we are looking for a suitable contact that matches with the author of the post
560 // This is done only for comments (See below explanation at "gcontact-id")
561 if ($arr['parent-uri'] != $arr['uri'])
562 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
564 // If not present then maybe the owner was found
565 if ($arr["contact-id"] == 0)
566 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
568 // Still missing? Then use the "self" contact of the current user
569 if ($arr["contact-id"] == 0) {
570 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
572 $arr["contact-id"] = $r[0]["id"];
574 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
577 if ($arr["gcontact-id"] == 0) {
578 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
579 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
580 // On comments the author is the better choice.
581 if ($arr['parent-uri'] === $arr['uri'])
582 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
583 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
585 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
586 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
589 if ($arr["author-id"] == 0)
590 $arr["author-id"] = get_contact($arr["author-link"], 0);
592 if ($arr["owner-id"] == 0)
593 $arr["owner-id"] = get_contact($arr["owner-link"], 0);
595 if ($arr['guid'] != "") {
596 // Checking if there is already an item with the same guid
597 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
598 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
599 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
602 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
607 // Check for hashtags in the body and repair or add hashtag links
608 item_body_set_hashtags($arr);
610 $arr['thr-parent'] = $arr['parent-uri'];
611 if ($arr['parent-uri'] === $arr['uri']) {
614 $allow_cid = $arr['allow_cid'];
615 $allow_gid = $arr['allow_gid'];
616 $deny_cid = $arr['deny_cid'];
617 $deny_gid = $arr['deny_gid'];
618 $notify_type = 'wall-new';
621 // find the parent and snarf the item id and ACLs
622 // and anything else we need to inherit
624 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
625 dbesc($arr['parent-uri']),
631 // is the new message multi-level threaded?
632 // even though we don't support it now, preserve the info
633 // and re-attach to the conversation parent.
635 if ($r[0]['uri'] != $r[0]['parent-uri']) {
636 $arr['parent-uri'] = $r[0]['parent-uri'];
637 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
638 ORDER BY `id` ASC LIMIT 1",
639 dbesc($r[0]['parent-uri']),
640 dbesc($r[0]['parent-uri']),
647 $parent_id = $r[0]['id'];
648 $parent_deleted = $r[0]['deleted'];
649 $allow_cid = $r[0]['allow_cid'];
650 $allow_gid = $r[0]['allow_gid'];
651 $deny_cid = $r[0]['deny_cid'];
652 $deny_gid = $r[0]['deny_gid'];
653 $arr['wall'] = $r[0]['wall'];
654 $notify_type = 'comment-new';
656 // if the parent is private, force privacy for the entire conversation
657 // This differs from the above settings as it subtly allows comments from
658 // email correspondents to be private even if the overall thread is not.
660 if ($r[0]['private'])
661 $arr['private'] = $r[0]['private'];
663 // Edge case. We host a public forum that was originally posted to privately.
664 // The original author commented, but as this is a comment, the permissions
665 // weren't fixed up so it will still show the comment as private unless we fix it here.
667 if ((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
671 // If its a post from myself then tag the thread as "mention"
672 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
673 $u = q("SELECT `nickname` FROM `user` WHERE `uid` = %d", intval($arr['uid']));
676 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
677 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
678 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
679 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
680 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
685 // Allow one to see reply tweets from status.net even when
686 // we don't have or can't see the original post.
689 logger('item_store: $force_parent=true, reply converted to top-level post.');
691 $arr['parent-uri'] = $arr['uri'];
694 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
702 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
704 dbesc($arr['network']),
708 if (dbm::is_result($r)) {
709 logger('duplicated item with the same uri found. '.print_r($arr,true));
713 // On Friendica and Diaspora the GUID is unique
714 if (in_array($arr['network'], array(NETWORK_DFRN, NETWORK_DIASPORA))) {
715 $r = q("SELECT `id` FROM `item` WHERE `guid` = '%s' AND `uid` = %d LIMIT 1",
719 if (dbm::is_result($r)) {
720 logger('duplicated item with the same guid found. '.print_r($arr,true));
724 // Check for an existing post with the same content. There seems to be a problem with OStatus.
725 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
727 dbesc($arr['network']),
728 dbesc($arr['created']),
729 intval($arr['contact-id']),
732 if (dbm::is_result($r)) {
733 logger('duplicated item with the same body found. '.print_r($arr,true));
738 // Is this item available in the global items (with uid=0)?
739 if ($arr["uid"] == 0) {
740 $arr["global"] = true;
742 // Set the global flag on all items if this was a global item entry
743 q("UPDATE `item` SET `global` = 1 WHERE `uri` = '%s'", dbesc($arr["uri"]));
745 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `uri` = '%s'", dbesc($arr["uri"]));
747 $arr["global"] = (count($isglobal) > 0);
751 if (strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
754 $private = $arr['private'];
756 $arr["allow_cid"] = $allow_cid;
757 $arr["allow_gid"] = $allow_gid;
758 $arr["deny_cid"] = $deny_cid;
759 $arr["deny_gid"] = $deny_gid;
760 $arr["private"] = $private;
761 $arr["deleted"] = $parent_deleted;
763 // Fill the cache field
764 put_item_in_cache($arr);
767 call_hooks('post_local',$arr);
769 call_hooks('post_remote',$arr);
771 if (x($arr,'cancel')) {
772 logger('item_store: post cancelled by plugin.');
776 // Check for already added items.
777 // There is a timing issue here that sometimes creates double postings.
778 // An unique index would help - but the limitations of MySQL (maximum size of index values) prevent this.
779 if ($arr["uid"] == 0) {
780 $r = qu("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = 0 LIMIT 1", dbesc(trim($arr['uri'])));
781 if (dbm::is_result($r)) {
782 logger('Global item already stored. URI: '.$arr['uri'].' on network '.$arr['network'], LOGGER_DEBUG);
787 // Store the unescaped version
792 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
795 q("START TRANSACTION;");
797 $r = dbq("INSERT INTO `item` (`"
798 . implode("`, `", array_keys($arr))
800 . implode("', '", array_values($arr))
806 // When the item was successfully stored we fetch the ID of the item.
807 if (dbm::is_result($r)) {
808 $r = q("SELECT LAST_INSERT_ID() AS `item-id`");
809 if (dbm::is_result($r)) {
810 $current_post = $r[0]['item-id'];
812 // This shouldn't happen
816 // This can happen - for example - if there are locking timeouts.
817 logger("Item wasn't stored - we quit here.");
822 if ($current_post == 0) {
823 // This is one of these error messages that never should occur.
824 logger("couldn't find created item - we better quit now.");
829 // How much entries have we created?
830 // We wouldn't need this query when we could use an unique index - but MySQL has length problems with them.
831 $r = q("SELECT COUNT(*) AS `entries` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s'",
834 dbesc($arr['network'])
837 if (!dbm::is_result($r)) {
838 // This shouldn't happen, since COUNT always works when the database connection is there.
839 logger("We couldn't count the stored entries. Very strange ...");
844 if ($r[0]["entries"] > 1) {
845 // There are duplicates. We delete our just created entry.
846 logger('Duplicated post occurred. uri = '.$arr['uri'].' uid = '.$arr['uid']);
848 // Yes, we could do a rollback here - but we are having many users with MyISAM.
849 q("DELETE FROM `item` WHERE `id` = %d", intval($current_post));
852 } elseif ($r[0]["entries"] == 0) {
853 // This really should never happen since we quit earlier if there were problems.
854 logger("Something is terribly wrong. We haven't found our created entry.");
859 logger('item_store: created item '.$current_post);
860 item_set_last_item($arr);
862 if (!$parent_id || ($arr['parent-uri'] === $arr['uri']))
863 $parent_id = $current_post;
866 $r = q("UPDATE `item` SET `parent` = %d WHERE `id` = %d",
868 intval($current_post)
871 $arr['id'] = $current_post;
872 $arr['parent'] = $parent_id;
874 // update the commented timestamp on the parent
875 // Only update "commented" if it is really a comment
876 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
877 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
878 dbesc(datetime_convert()),
879 dbesc(datetime_convert()),
883 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
884 dbesc(datetime_convert()),
890 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
891 // We can check for this condition when we decode and encode the stuff again.
892 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
893 $dsprsig->signature = base64_decode($dsprsig->signature);
894 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
897 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
898 intval($current_post),
899 dbesc($dsprsig->signed_text),
900 dbesc($dsprsig->signature),
901 dbesc($dsprsig->signer)
905 $deleted = tag_deliver($arr['uid'],$current_post);
907 // current post can be deleted if is for a community page and no mention are
909 if (!$deleted AND !$dontcache) {
911 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
912 if (count($r) == 1) {
914 call_hooks('post_local_end', $r[0]);
916 call_hooks('post_remote_end', $r[0]);
918 logger('item_store: new item not found in DB, id ' . $current_post);
921 if ($arr['parent-uri'] === $arr['uri']) {
922 add_thread($current_post);
924 update_thread($parent_id);
929 // Due to deadlock issues with the "term" table we are doing these steps after the commit.
930 // This is not perfect - but a workable solution until we found the reason for the problem.
931 create_tags_from_item($current_post);
932 create_files_from_item($current_post);
934 // If this is now the last-child, force all _other_ children of this parent to *not* be last-child
935 // It is done after the transaction to avoid dead locks.
936 if ($arr['last-child']) {
937 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
940 intval($current_post)
944 if ($arr['parent-uri'] === $arr['uri']) {
945 add_shadow_thread($current_post);
947 add_shadow_entry($current_post);
950 check_item_notification($current_post, $uid);
953 proc_run(PRIORITY_HIGH, "include/notifier.php", $notify_type, $current_post);
955 return $current_post;
959 * @brief Set "success_update" and "last-item" to the date of the last time we heard from this contact
961 * This can be used to filter for inactive contacts.
962 * Only do this for public postings to avoid privacy problems, since poco data is public.
963 * Don't set this value if it isn't from the owner (could be an author that we don't know)
965 * @param array $arr Contains the just posted item record
967 function item_set_last_item($arr) {
969 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
971 // Is it a forum? Then we don't care about the rules from above
972 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
973 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
974 intval($arr['contact-id']));
981 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
982 dbesc($arr['received']),
983 dbesc($arr['received']),
984 intval($arr['contact-id'])
987 // Now do the same for the system wide contacts with uid=0
988 if (!$arr['private']) {
989 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
990 dbesc($arr['received']),
991 dbesc($arr['received']),
992 intval($arr['owner-id'])
995 if ($arr['owner-id'] != $arr['author-id']) {
996 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
997 dbesc($arr['received']),
998 dbesc($arr['received']),
999 intval($arr['author-id'])
1005 function item_body_set_hashtags(&$item) {
1007 $tags = get_tags($item["body"]);
1013 // This sorting is important when there are hashtags that are part of other hashtags
1014 // Otherwise there could be problems with hashtags like #test and #test2
1019 $URLSearchString = "^\[\]";
1021 // All hashtags should point to the home server
1022 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1023 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1025 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1026 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1028 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1029 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1031 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1034 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1036 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1039 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1041 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1044 // Repair recursive urls
1045 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1046 "#$2", $item["body"]);
1049 foreach($tags as $tag) {
1050 if (strpos($tag,'#') !== 0)
1053 if (strpos($tag,'[url='))
1056 $basetag = str_replace('_',' ',substr($tag,1));
1058 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1060 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1062 if (!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1063 if (strlen($item["tag"]))
1064 $item["tag"] = ','.$item["tag"];
1065 $item["tag"] = $newtag.$item["tag"];
1069 // Convert back the masked hashtags
1070 $item["body"] = str_replace("#", "#", $item["body"]);
1073 function get_item_guid($id) {
1074 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1076 return($r[0]["guid"]);
1081 function get_item_id($guid, $uid = 0) {
1087 $uid == local_user();
1089 // Does the given user have this item?
1091 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1092 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1093 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1096 $nick = $r[0]["nickname"];
1100 // Or is it anywhere on the server?
1102 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1103 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1104 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1105 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1106 AND `item`.`private` = 0 AND `item`.`wall` = 1
1107 AND `item`.`guid` = '%s'", dbesc($guid));
1110 $nick = $r[0]["nickname"];
1113 return(array("nick" => $nick, "id" => $id));
1117 function get_item_contact($item,$contacts) {
1118 if (! count($contacts) || (! is_array($item)))
1120 foreach($contacts as $contact) {
1121 if ($contact['id'] == $item['contact-id']) {
1123 break; // NOTREACHED
1130 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1132 * @param int $item_id
1133 * @return bool true if item was deleted, else false
1135 function tag_deliver($uid,$item_id) {
1143 $u = q("select * from user where uid = %d limit 1",
1149 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1150 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1153 $i = q("select * from item where id = %d and uid = %d limit 1",
1162 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1164 // Diaspora uses their own hardwired link URL in @-tags
1165 // instead of the one we supply with webfinger
1167 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1169 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1171 foreach($matches as $mtch) {
1172 if (link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1174 logger('tag_deliver: mention found: ' . $mtch[2]);
1180 if ( ($community_page || $prvgroup) &&
1181 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1182 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1184 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1185 q("DELETE FROM item WHERE id = %d and uid = %d",
1194 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1196 call_hooks('tagged', $arr);
1198 if ((! $community_page) && (! $prvgroup))
1202 // tgroup delivery - setup a second delivery chain
1203 // prevent delivery looping - only proceed
1204 // if the message originated elsewhere and is a top-level post
1206 if (($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1209 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1212 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1213 intval($u[0]['uid'])
1218 // also reset all the privacy bits to the forum default permissions
1220 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1222 $forum_mode = (($prvgroup) ? 2 : 1);
1224 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1225 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1226 intval($forum_mode),
1227 dbesc($c[0]['name']),
1228 dbesc($c[0]['url']),
1229 dbesc($c[0]['thumb']),
1231 dbesc($u[0]['allow_cid']),
1232 dbesc($u[0]['allow_gid']),
1233 dbesc($u[0]['deny_cid']),
1234 dbesc($u[0]['deny_gid']),
1237 update_thread($item_id);
1239 proc_run(PRIORITY_HIGH,'include/notifier.php', 'tgroup', $item_id);
1245 function tgroup_check($uid,$item) {
1251 // check that the message originated elsewhere and is a top-level post
1253 if (($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1257 $u = q("select * from user where uid = %d limit 1",
1263 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1264 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1267 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1269 // Diaspora uses their own hardwired link URL in @-tags
1270 // instead of the one we supply with webfinger
1272 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1274 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1276 foreach($matches as $mtch) {
1277 if (link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1279 logger('tgroup_check: mention found: ' . $mtch[2]);
1287 if ((! $community_page) && (! $prvgroup))
1294 This function returns true if $update has an edited timestamp newer
1295 than $existing, i.e. $update contains new data which should override
1296 what's already there. If there is no timestamp yet, the update is
1297 assumed to be newer. If the update has no timestamp, the existing
1298 item is assumed to be up-to-date. If the timestamps are equal it
1299 assumes the update has been seen before and should be ignored.
1301 function edited_timestamp_is_newer($existing, $update) {
1302 if (!x($existing,'edited') || !$existing['edited']) {
1305 if (!x($update,'edited') || !$update['edited']) {
1308 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1309 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1310 return (strcmp($existing_edited, $update_edited) < 0);
1315 * consume_feed - process atom feed and update anything/everything we might need to update
1317 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1319 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1320 * It is this person's stuff that is going to be updated.
1321 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1322 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1323 * have a contact record.
1324 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1325 * might not) try and subscribe to it.
1326 * $datedir sorts in reverse order
1327 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1328 * imported prior to its children being seen in the stream unless we are certain
1329 * of how the feed is arranged/ordered.
1330 * With $pass = 1, we only pull parent items out of the stream.
1331 * With $pass = 2, we only pull children (comments/likes).
1333 * So running this twice, first with pass 1 and then with pass 2 will do the right
1334 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1335 * model where comments can have sub-threads. That would require some massive sorting
1336 * to get all the feed items into a mostly linear ordering, and might still require
1340 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1341 if ($contact['network'] === NETWORK_OSTATUS) {
1343 // Test - remove before flight
1344 //$tempfile = tempnam(get_temppath(), "ostatus2");
1345 //file_put_contents($tempfile, $xml);
1346 logger("Consume OStatus messages ", LOGGER_DEBUG);
1347 ostatus::import($xml,$importer,$contact, $hub);
1352 if ($contact['network'] === NETWORK_FEED) {
1354 logger("Consume feeds", LOGGER_DEBUG);
1355 feed_import($xml,$importer,$contact, $hub);
1360 if ($contact['network'] === NETWORK_DFRN) {
1361 logger("Consume DFRN messages", LOGGER_DEBUG);
1363 $r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
1364 `contact`.`pubkey` AS `cpubkey`,
1365 `contact`.`prvkey` AS `cprvkey`,
1366 `contact`.`thumb` AS `thumb`,
1367 `contact`.`url` as `url`,
1368 `contact`.`name` as `senderName`,
1371 LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
1372 WHERE `contact`.`id` = %d AND `user`.`uid` = %d",
1373 dbesc($contact["id"]), dbesc($importer["uid"])
1376 logger("Now import the DFRN feed");
1377 dfrn::import($xml,$r[0], true);
1383 function item_is_remote_self($contact, &$datarray) {
1386 if (!$contact['remote_self'])
1389 // Prevent the forwarding of posts that are forwarded
1390 if ($datarray["extid"] == NETWORK_DFRN)
1393 // Prevent to forward already forwarded posts
1394 if ($datarray["app"] == $a->get_hostname())
1397 // Only forward posts
1398 if ($datarray["verb"] != ACTIVITY_POST)
1401 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
1404 $datarray2 = $datarray;
1405 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
1406 if ($contact['remote_self'] == 2) {
1407 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
1408 intval($contact['uid']));
1410 $datarray['contact-id'] = $r[0]["id"];
1412 $datarray['owner-name'] = $r[0]["name"];
1413 $datarray['owner-link'] = $r[0]["url"];
1414 $datarray['owner-avatar'] = $r[0]["thumb"];
1416 $datarray['author-name'] = $datarray['owner-name'];
1417 $datarray['author-link'] = $datarray['owner-link'];
1418 $datarray['author-avatar'] = $datarray['owner-avatar'];
1421 if ($contact['network'] != NETWORK_FEED) {
1422 $datarray["guid"] = get_guid(32);
1423 unset($datarray["plink"]);
1424 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
1425 $datarray["parent-uri"] = $datarray["uri"];
1426 $datarray["extid"] = $contact['network'];
1427 $urlpart = parse_url($datarray2['author-link']);
1428 $datarray["app"] = $urlpart["host"];
1430 $datarray['private'] = 0;
1433 if ($contact['network'] != NETWORK_FEED) {
1434 // Store the original post
1435 $r = item_store($datarray2, false, false);
1436 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
1438 $datarray["app"] = "Feed";
1443 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
1444 $url = notags(trim($datarray['author-link']));
1445 $name = notags(trim($datarray['author-name']));
1446 $photo = notags(trim($datarray['author-avatar']));
1448 if (is_object($item)) {
1449 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
1450 if ($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
1451 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
1455 if (is_array($contact)) {
1456 if (($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
1457 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
1458 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
1459 intval(CONTACT_IS_FRIEND),
1460 intval($contact['id']),
1461 intval($importer['uid'])
1464 // send email notification to owner?
1467 // create contact record
1469 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
1470 `blocked`, `readonly`, `pending`, `writable`)
1471 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
1472 intval($importer['uid']),
1473 dbesc(datetime_convert()),
1475 dbesc(normalise_link($url)),
1479 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
1480 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
1482 $r = q("SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
1483 intval($importer['uid']),
1487 $contact_record = $r[0];
1488 update_contact_avatar($photo, $importer["uid"], $contact_record["id"], true);
1492 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
1493 intval($importer['uid'])
1496 if (count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1498 // create notification
1499 $hash = random_string();
1501 if (is_array($contact_record)) {
1502 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
1503 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
1504 intval($importer['uid']),
1505 intval($contact_record['id']),
1507 dbesc(datetime_convert())
1511 $def_gid = get_default_group($importer['uid'], $contact_record["network"]);
1513 if (intval($def_gid))
1514 group_add_member($importer['uid'],'',$contact_record['id'],$def_gid);
1516 if (($r[0]['notify-flags'] & NOTIFY_INTRO) &&
1517 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
1520 'type' => NOTIFY_INTRO,
1521 'notify_flags' => $r[0]['notify-flags'],
1522 'language' => $r[0]['language'],
1523 'to_name' => $r[0]['username'],
1524 'to_email' => $r[0]['email'],
1525 'uid' => $r[0]['uid'],
1526 'link' => $a->get_baseurl() . '/notifications/intro',
1527 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
1528 'source_link' => $contact_record['url'],
1529 'source_photo' => $contact_record['photo'],
1530 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
1535 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1536 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
1537 intval($importer['uid']),
1545 function lose_follower($importer,$contact,$datarray = array(),$item = "") {
1547 if (($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
1548 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1549 intval(CONTACT_IS_SHARING),
1550 intval($contact['id'])
1553 contact_remove($contact['id']);
1557 function lose_sharer($importer,$contact,$datarray = array(),$item = "") {
1559 if (($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
1560 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1561 intval(CONTACT_IS_FOLLOWER),
1562 intval($contact['id'])
1565 contact_remove($contact['id']);
1569 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
1573 if (is_array($importer)) {
1574 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
1575 intval($importer['uid'])
1579 // Diaspora has different message-ids in feeds than they do
1580 // through the direct Diaspora protocol. If we try and use
1581 // the feed, we'll get duplicates. So don't.
1583 if ((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
1586 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
1588 // Use a single verify token, even if multiple hubs
1590 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
1592 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
1594 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
1596 if (!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
1597 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
1598 dbesc($verify_token),
1599 intval($contact['id'])
1603 post_url($url,$params);
1605 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
1611 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
1613 if (get_config('system','disable_embedded'))
1618 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
1619 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
1624 $img_start = strpos($orig_body, '[img');
1625 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1626 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1627 while( ($img_st_close !== false) && ($img_len !== false) ) {
1629 $img_st_close++; // make it point to AFTER the closing bracket
1630 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
1632 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
1635 if (stristr($image , $site . '/photo/')) {
1636 // Only embed locally hosted photos
1638 $i = basename($image);
1639 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
1640 $x = strpos($i,'-');
1643 $res = substr($i,$x+1);
1644 $i = substr($i,0,$x);
1645 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
1652 // Check to see if we should replace this photo link with an embedded image
1653 // 1. No need to do so if the photo is public
1654 // 2. If there's a contact-id provided, see if they're in the access list
1655 // for the photo. If so, embed it.
1656 // 3. Otherwise, if we have an item, see if the item permissions match the photo
1657 // permissions, regardless of order but first check to see if they're an exact
1658 // match to save some processing overhead.
1660 if (has_permissions($r[0])) {
1662 $recips = enumerate_permissions($r[0]);
1663 if (in_array($cid, $recips)) {
1667 if (compare_permissions($item,$r[0]))
1672 $data = $r[0]['data'];
1673 $type = $r[0]['type'];
1675 // If a custom width and height were specified, apply before embedding
1676 if (preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
1677 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
1679 $width = intval($match[1]);
1680 $height = intval($match[2]);
1682 $ph = new Photo($data, $type);
1683 if ($ph->is_valid()) {
1684 $ph->scaleImage(max($width, $height));
1685 $data = $ph->imageString();
1686 $type = $ph->getType();
1690 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
1691 $image = 'data:' . $type . ';base64,' . base64_encode($data);
1692 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
1698 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
1699 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
1700 if ($orig_body === false)
1703 $img_start = strpos($orig_body, '[img');
1704 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1705 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1708 $new_body = $new_body . $orig_body;
1713 function has_permissions($obj) {
1714 if (($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
1719 function compare_permissions($obj1,$obj2) {
1720 // first part is easy. Check that these are exactly the same.
1721 if (($obj1['allow_cid'] == $obj2['allow_cid'])
1722 && ($obj1['allow_gid'] == $obj2['allow_gid'])
1723 && ($obj1['deny_cid'] == $obj2['deny_cid'])
1724 && ($obj1['deny_gid'] == $obj2['deny_gid']))
1727 // This is harder. Parse all the permissions and compare the resulting set.
1729 $recipients1 = enumerate_permissions($obj1);
1730 $recipients2 = enumerate_permissions($obj2);
1733 if ($recipients1 == $recipients2)
1738 // returns an array of contact-ids that are allowed to see this object
1740 function enumerate_permissions($obj) {
1741 $allow_people = expand_acl($obj['allow_cid']);
1742 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
1743 $deny_people = expand_acl($obj['deny_cid']);
1744 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
1745 $recipients = array_unique(array_merge($allow_people,$allow_groups));
1746 $deny = array_unique(array_merge($deny_people,$deny_groups));
1747 $recipients = array_diff($recipients,$deny);
1751 function item_getfeedtags($item) {
1754 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1756 for($x = 0; $x < $cnt; $x ++) {
1757 if ($matches[1][$x])
1758 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
1762 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1764 for($x = 0; $x < $cnt; $x ++) {
1765 if ($matches[1][$x])
1766 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
1772 function item_expire($uid, $days, $network = "", $force = false) {
1774 if ((! $uid) || ($days < 1))
1777 // $expire_network_only = save your own wall posts
1778 // and just expire conversations started by others
1780 $expire_network_only = get_pconfig($uid,'expire','network_only');
1781 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
1783 if ($network != "") {
1784 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
1785 // There is an index "uid_network_received" but not "uid_network_created"
1786 // This avoids the creation of another index just for one purpose.
1787 // And it doesn't really matter wether to look at "received" or "created"
1788 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1790 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1792 $r = q("SELECT `file`, `resource-id`, `starred`, `type`, `id` FROM `item`
1793 WHERE `uid` = %d $range
1804 $expire_items = get_pconfig($uid, 'expire','items');
1805 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
1807 // Forcing expiring of items - but not notes and marked items
1809 $expire_items = true;
1811 $expire_notes = get_pconfig($uid, 'expire','notes');
1812 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
1814 $expire_starred = get_pconfig($uid, 'expire','starred');
1815 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
1817 $expire_photos = get_pconfig($uid, 'expire','photos');
1818 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
1820 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
1822 foreach($r as $item) {
1824 // don't expire filed items
1826 if (strpos($item['file'],'[') !== false)
1829 // Only expire posts, not photos and photo comments
1831 if ($expire_photos==0 && strlen($item['resource-id']))
1833 if ($expire_starred==0 && intval($item['starred']))
1835 if ($expire_notes==0 && $item['type']=='note')
1837 if ($expire_items==0 && $item['type']!='note')
1840 drop_item($item['id'],false);
1843 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1848 function drop_items($items) {
1851 if (! local_user() && ! remote_user())
1854 if (count($items)) {
1855 foreach($items as $item) {
1856 $owner = drop_item($item,false);
1857 if ($owner && ! $uid)
1862 // multiple threads may have been deleted, send an expire notification
1865 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1869 function drop_item($id,$interactive = true) {
1873 // locate item to be deleted
1875 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
1882 notice( t('Item not found.') . EOL);
1883 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1888 $owner = $item['uid'];
1892 // check if logged in user is either the author or owner of this item
1894 if (is_array($_SESSION['remote'])) {
1895 foreach($_SESSION['remote'] as $visitor) {
1896 if ($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
1897 $cid = $visitor['cid'];
1904 if ((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
1906 // Check if we should do HTML-based delete confirmation
1907 if ($_REQUEST['confirm']) {
1908 // <form> can't take arguments in its "action" parameter
1909 // so add any arguments as hidden inputs
1910 $query = explode_querystring($a->query_string);
1912 foreach($query['args'] as $arg) {
1913 if (strpos($arg, 'confirm=') === false) {
1914 $arg_parts = explode('=', $arg);
1915 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
1919 return replace_macros(get_markup_template('confirm.tpl'), array(
1921 '$message' => t('Do you really want to delete this item?'),
1922 '$extra_inputs' => $inputs,
1923 '$confirm' => t('Yes'),
1924 '$confirm_url' => $query['base'],
1925 '$confirm_name' => 'confirmed',
1926 '$cancel' => t('Cancel'),
1929 // Now check how the user responded to the confirmation query
1930 if ($_REQUEST['canceled']) {
1931 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1934 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
1937 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
1938 dbesc(datetime_convert()),
1939 dbesc(datetime_convert()),
1942 create_tags_from_item($item['id']);
1943 create_files_from_item($item['id']);
1944 delete_thread($item['id'], $item['parent-uri']);
1946 // clean up categories and tags so they don't end up as orphans
1949 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1951 foreach($matches as $mtch) {
1952 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
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],false);
1965 // If item is a link to a photo resource, nuke all the associated photos
1966 // (visitors will not have photo resources)
1967 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
1968 // generate a resource-id and therefore aren't intimately linked to the item.
1970 if (strlen($item['resource-id'])) {
1971 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
1972 dbesc($item['resource-id']),
1973 intval($item['uid'])
1975 // ignore the result
1978 // If item is a link to an event, nuke the event record.
1980 if (intval($item['event-id'])) {
1981 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
1982 intval($item['event-id']),
1983 intval($item['uid'])
1985 // ignore the result
1988 // If item has attachments, drop them
1990 foreach(explode(",",$item['attach']) as $attach){
1991 preg_match("|attach/(\d+)|", $attach, $matches);
1992 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
1993 intval($matches[1]),
1996 // ignore the result
2000 // clean up item_id and sign meta-data tables
2003 // Old code - caused very long queries and warning entries in the mysql logfiles:
2005 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
2006 intval($item['id']),
2007 intval($item['uid'])
2010 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
2011 intval($item['id']),
2012 intval($item['uid'])
2016 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
2018 // Creating list of parents
2019 $r = q("select id from item where parent = %d and uid = %d",
2020 intval($item['id']),
2021 intval($item['uid'])
2026 foreach ($r AS $row) {
2027 if ($parentid != "")
2030 $parentid .= $row["id"];
2034 if ($parentid != "") {
2035 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
2037 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
2040 // If it's the parent of a comment thread, kill all the kids
2042 if ($item['uri'] == $item['parent-uri']) {
2043 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
2044 WHERE `parent-uri` = '%s' AND `uid` = %d ",
2045 dbesc(datetime_convert()),
2046 dbesc(datetime_convert()),
2047 dbesc($item['parent-uri']),
2048 intval($item['uid'])
2050 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
2051 create_files_from_itemuri($item['parent-uri'], $item['uid']);
2052 delete_thread_uri($item['parent-uri'], $item['uid']);
2053 // ignore the result
2055 // ensure that last-child is set in case the comment that had it just got wiped.
2056 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2057 dbesc(datetime_convert()),
2058 dbesc($item['parent-uri']),
2059 intval($item['uid'])
2061 // who is the last child now?
2062 $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",
2063 dbesc($item['parent-uri']),
2064 intval($item['uid'])
2067 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2073 $drop_id = intval($item['id']);
2075 // send the notification upstream/downstream as the case may be
2077 proc_run(PRIORITY_HIGH,"include/notifier.php", "drop", $drop_id);
2081 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2086 notice( t('Permission denied.') . EOL);
2087 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2094 function first_post_date($uid,$wall = false) {
2095 $r = q("select id, created from item
2096 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
2098 order by created asc limit 1",
2100 intval($wall ? 1 : 0)
2103 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
2104 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
2109 /* modified posted_dates() {below} to arrange the list in years */
2110 function list_post_dates($uid, $wall) {
2111 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2113 $dthen = first_post_date($uid, $wall);
2117 // Set the start and end date to the beginning of the month
2118 $dnow = substr($dnow,0,8).'01';
2119 $dthen = substr($dthen,0,8).'01';
2123 // Starting with the current month, get the first and last days of every
2124 // month down to and including the month of the first post
2125 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2126 $dyear = intval(substr($dnow,0,4));
2127 $dstart = substr($dnow,0,8) . '01';
2128 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2129 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2130 $end_month = datetime_convert('','',$dend,'Y-m-d');
2131 $str = day_translate(datetime_convert('','',$dnow,'F'));
2133 $ret[$dyear] = array();
2134 $ret[$dyear][] = array($str,$end_month,$start_month);
2135 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2140 function posted_dates($uid,$wall) {
2141 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2143 $dthen = first_post_date($uid,$wall);
2147 // Set the start and end date to the beginning of the month
2148 $dnow = substr($dnow,0,8).'01';
2149 $dthen = substr($dthen,0,8).'01';
2152 // Starting with the current month, get the first and last days of every
2153 // month down to and including the month of the first post
2154 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2155 $dstart = substr($dnow,0,8) . '01';
2156 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2157 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2158 $end_month = datetime_convert('','',$dend,'Y-m-d');
2159 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
2160 $ret[] = array($str,$end_month,$start_month);
2161 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2167 function posted_date_widget($url,$uid,$wall) {
2170 if (! feature_enabled($uid,'archives'))
2173 // For former Facebook folks that left because of "timeline"
2175 /* if ($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
2178 $visible_years = get_pconfig($uid,'system','archive_visible_years');
2179 if (! $visible_years)
2182 $ret = list_post_dates($uid,$wall);
2187 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
2188 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
2190 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
2191 '$title' => t('Archives'),
2192 '$size' => $visible_years,
2193 '$cutoff_year' => $cutoff_year,
2194 '$cutoff' => $cutoff,
2197 '$showmore' => t('show more')